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

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 JiraConfiguration jiraConfiguration;
/**
* Currency code according to ISO-4217 (3 letters)
*/
@ -505,12 +503,4 @@ public class Configuration extends BaseEntity {
this.secondsPlanningWarning = secondsPlanningWarning;
}
public JiraConfiguration getJiraConfiguration() {
return jiraConfiguration;
}
public void setJiraConfiguration(JiraConfiguration jiraConfiguration) {
this.jiraConfiguration = jiraConfiguration;
}
}

View file

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

View file

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

View file

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

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 {
String strQuery = "SELECT oa.order.id "
+ "FROM OrderAuthorization oa "
+ "WHERE oa.user = :user "
+ "OR oa.profile IN (:profiles) ";
+ "WHERE oa.user = :user ";
if (!user.getProfiles().isEmpty()) {
strQuery += "OR oa.profile IN (:profiles) ";
}
Query query = getSession().createQuery(strQuery);
query.setParameter("user", user);
query.setParameterList("profiles", user.getProfiles());
if (!user.getProfiles().isEmpty()) {
query.setParameterList("profiles", user.getProfiles());
}
return query.list();
}

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

View file

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

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_HOURS_TYPES;
import static org.libreplan.business.users.entities.UserRole.ROLE_HOURS_WORKED_PER_RESOURCE_REPORT;
import static org.libreplan.business.users.entities.UserRole.ROLE_JOB_SCHEDULING;
import static org.libreplan.business.users.entities.UserRole.ROLE_LABELS;
import static org.libreplan.business.users.entities.UserRole.ROLE_MACHINES;
import static org.libreplan.business.users.entities.UserRole.ROLE_MAIN_SETTINGS;
@ -74,7 +75,7 @@ import org.libreplan.business.users.entities.UserRole;
public enum PredefinedProfiles {
SYSTEMS_ADMINISTRATOR("Systems Administrator", ROLE_MAIN_SETTINGS,
ROLE_USER_ACCOUNTS, ROLE_PROFILES),
ROLE_USER_ACCOUNTS, ROLE_PROFILES, ROLE_JOB_SCHEDULING),
PROJECT_MANAGER("Project Manager", ROLE_READ_ALL_PROJECTS,
ROLE_EDIT_ALL_PROJECTS, ROLE_CREATE_PROJECTS, ROLE_PLANNING,

View file

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

View file

@ -76,5 +76,12 @@ public interface IWorkReportLineDAO extends
List<WorkReportLine> findByOrderElementAndWorkReports(
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();
}
@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" />
</changeSet>
<!-- Jira configuration-->
<changeSet id="add-new-column-jira_activated" author="miciele">
<comment>
Add new column jira_activated with default value FALSE to configuration table
</comment>
<addColumn tableName="configuration">
<column name="jira_activated" type="BOOLEAN" />
</addColumn>
<addDefaultValue tableName="configuration" columnName="jira_activated"
defaultValueBoolean="FALSE" />
<addNotNullConstraint tableName="configuration"
columnName="jira_activated"
defaultNullValue="FALSE"
columnDataType="BOOLEAN" />
<!-- order sync info -->
<changeSet author="miciele" id="create-table-order-sync-info">
<comment>Create new table order_sync_info</comment>
<createTable tableName="order_sync_info">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_sync_date" type="DATETIME" >
<constraints nullable="false"/>
</column>
<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 id="add-new-column-jira_url" author="miciele">
<comment>
Add new column jira_url in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_url" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<!-- connector -->
<changeSet author="rego" id="create-tables-related-to-connector-entity">
<comment>Create tables related to Connector entity</comment>
<createTable tableName="connector">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false" primaryKey="true" primaryKeyName="connector_pkey" />
</column>
<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">
<comment>
Add new column jira_label_url in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_label_url" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<createTable tableName="connector_property">
<column name="connector_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="connector_property_position" type="INTEGER">
<constraints nullable="false" />
</column>
<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">
<comment>
Add new column jira_user_id in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_user_id" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<addPrimaryKey
columnNames="connector_id,connector_property_position"
constraintName="connector_property_pkey"
tableName="connector_property"/>
<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
constraintName="configuration_jira_connector_type_of_work_hours_fkey"
baseTableName="configuration"
baseColumnNames="jira_connector_type_of_work_hours"
referencedTableName="type_of_work_hours"
referencedColumnNames="id" />
baseColumnNames="connector_id"
baseTableName="connector_property"
constraintName="connector_property_connector_id_fkey"
deferrable="false" initiallyDeferred="false"
onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id"
referencedTableName="connector"
referencesUniqueColumn="false"/>
</changeSet>
<changeSet id="rename-column-jira_label_url-in-configuration"
author="mrego">
<comment>
Rename column jira_label_url to jira_labels in configuration table
</comment>
<renameColumn tableName="configuration"
oldColumnName="jira_label_url"
newColumnName="jira_labels"
columnDataType="VARCHAR(255)" />
</changeSet>
<changeSet id="change-column-jira_labels-in-configuration-to-text"
author="mrego" dbms="postgresql">
<comment>Change column jira_labels in configuration to TEXT</comment>
<modifyDataType tableName="configuration" columnName="jira_labels"
newDataType="TEXT" />
</changeSet>
<changeSet id="change-column-jira_labels-in-configuration-to-text-in-mysql"
author="mrego" dbms="mysql">
<comment>Change column jira_labels in configuration to TEXT in MySQL</comment>
<sql>ALTER TABLE configuration MODIFY jira_labels TEXT</sql>
<!-- scheduler configuration -->
<changeSet author="miciele" id="create-table-job-scheduler-configuration">
<comment>Create new table job_scheduler_configuration</comment>
<createTable tableName="job_scheduler_configuration">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false" primaryKey="true" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="job_group" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
<column name="job_name" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
<column name="cron_expression" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
<column name="job_class_name" type="INTEGER" >
<constraints nullable="false" />
</column>
<column name="connector_name" type="VARCHAR(255)" />
<column name="schedule" type="BOOLEAN" />
</createTable>
</changeSet>
<changeSet id="add-projects_filter_period_since-column-to-user_table"
@ -387,4 +381,4 @@
onDelete="SET NULL"/>
</changeSet>
</databaseChangeLog>
</databaseChangeLog>

View file

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

View file

@ -120,18 +120,6 @@
</set>
</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>
</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>
</property>
<!-- extra column to hold imported label -->
<property name="importedLabel" column="imported_label"/>
<!-- Not indexed -->
<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"/>
</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>

View file

@ -330,6 +330,11 @@
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<!-- QuartzJobBean in spring-context-support.jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- Spring security -->
<dependency>
<groupId>org.springframework.security</groupId>
@ -481,5 +486,10 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- Quartz framework -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
</dependencies>
</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.DirectAdvanceAssignment;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.importers.jira.IssueDTO;
/**
@ -48,8 +50,10 @@ public interface IJiraOrderElementSynchronizer {
* https://jira.atlassian.com/browse/JRA-29409
*
* @return A list of labels
* @throws ConnectorException
* if connector not found
*/
List<String> getAllJiraLabels();
List<String> getAllJiraLabels() throws ConnectorException;
/**
* Get all jira issues based on the specified <code>label</code> parameter
@ -59,8 +63,10 @@ public interface IJiraOrderElementSynchronizer {
* search criteria for jira issues
*
* @return list of jira issues
* @throws ConnectorException
* if connector not found or contains invalid connection values
*/
List<IssueDTO> getJiraIssues(String label);
List<IssueDTO> getJiraIssues(String label) throws ConnectorException;
/**
* Synchronizes the list of {@link OrderElement}s,
@ -82,9 +88,42 @@ public interface IJiraOrderElementSynchronizer {
*/
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
*/
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 org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.workreports.entities.WorkReportType;
import org.libreplan.importers.jira.IssueDTO;
@ -42,7 +43,6 @@ public interface IJiraTimesheetSynchronizer {
/**
* Synchronize jira timesheet with the specified jira <code>issues</code> .
*
*
* Loop through all jira <code>issues</code> and check if timesheet is
* already exist for the specified issue item. If it is, update the
* timesheet with that issue item. If not create new one
@ -51,12 +51,14 @@ public interface IJiraTimesheetSynchronizer {
* the jira issues
* @param order
* an existing order
* @throws ConnectorException
* if not valid connector or connector contains invalid values
*/
void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order);
void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) throws ConnectorException;
/**
* 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;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.MalformedURLException;
@ -28,8 +30,11 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate;
import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes;
import org.libreplan.business.advance.entities.AdvanceMeasurement;
@ -37,18 +42,26 @@ import org.libreplan.business.advance.entities.AdvanceType;
import org.libreplan.business.advance.entities.DirectAdvanceAssignment;
import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException;
import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalAdvanceException;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.JiraConfiguration;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.orders.daos.IOrderSyncInfoDAO;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.importers.jira.IssueDTO;
import org.libreplan.importers.jira.StatusDTO;
import org.libreplan.importers.jira.TimeTrackingDTO;
import org.libreplan.importers.jira.WorkLogDTO;
import org.libreplan.importers.jira.WorkLogItemDTO;
import org.libreplan.web.orders.IOrderModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
@ -64,17 +77,36 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer {
private static final Log LOG = LogFactory
.getLog(JiraOrderElementSynchronizer.class);
private SynchronizationInfo synchronizationInfo;
@Autowired
private IConfigurationDAO configurationDAO;
private IConnectorDAO connectorDAO;
private JiraSyncInfo jiraSyncInfo;
@Autowired
private IOrderSyncInfoDAO orderSyncInfoDAO;
@Autowired
private IAdHocTransactionService adHocTransactionService;
@Autowired
private IOrderModel orderModel;
@Autowired
private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer;
@Override
@Transactional(readOnly = true)
public List<String> getAllJiraLabels() {
String jiraLabels = configurationDAO.getConfiguration()
.getJiraConfiguration().getJiraLabels();
public List<String> getAllJiraLabels() throws ConnectorException {
Connector connector = getJiraConnector();
if (connector == null) {
throw new ConnectorException(_("JIRA connector not found"));
}
String jiraLabels = connector.getPropertiesAsMap().get(
PredefinedConnectorProperties.JIRA_LABELS);
String labels;
try {
@ -88,13 +120,39 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Override
@Transactional(readOnly = true)
public List<IssueDTO> getJiraIssues(String label) {
JiraConfiguration jiraConfiguration = configurationDAO
.getConfiguration().getJiraConfiguration();
public List<IssueDTO> getJiraIssues(String label) throws ConnectorException {
String url = jiraConfiguration.getJiraUrl();
String username = jiraConfiguration.getJiraUserId();
String password = jiraConfiguration.getJiraPassword();
Connector connector = getJiraConnector();
if (connector == null) {
throw new ConnectorException(_("JIRA connector not found"));
}
if (!connector.areConnectionValuesValid()) {
throw new ConnectorException(
_("Connection values of JIRA connector are invalid"));
}
return getJiraIssues(label, connector);
}
/**
* Gets all jira issues for the specified <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 query = "labels=" + label;
@ -109,17 +167,20 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Transactional(readOnly = true)
public void syncOrderElementsWithJiraIssues(List<IssueDTO> issues, Order order) {
jiraSyncInfo = new JiraSyncInfo();
synchronizationInfo = new SynchronizationInfo(_(
"Synchronization order {0}", order.getName()));
for (IssueDTO issue : issues) {
String code = JiraConfiguration.CODE_PREFIX + order.getCode() + "-"
String code = PredefinedConnectorProperties.JIRA_CODE_PREFIX
+ order.getCode() + "-"
+ issue.getKey();
String name = issue.getFields().getSummary();
OrderLine orderLine = syncOrderLine(order, code, name);
if (orderLine == null) {
jiraSyncInfo.addSyncFailedReason("Order-element for '"
+ issue.getKey() + "' issue not found");
synchronizationInfo.addFailedReason(_(
"Order-element for \"{0}\" issue not found",
issue.getKey()));
continue;
}
@ -129,8 +190,9 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
.getTimetracking(), loggedHours);
if (estimatedHours.isZero()) {
jiraSyncInfo.addSyncFailedReason("Estimated time for '"
+ issue.getKey() + "' issue is 0");
synchronizationInfo.addFailedReason(_(
"Estimated time for \"{0}\" issue is 0",
issue.getKey()));
continue;
}
@ -217,15 +279,16 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
WorkLogDTO workLog = issue.getFields().getWorklog();
if (workLog == null) {
jiraSyncInfo.addSyncFailedReason("No worklogs found for '"
+ issue.getKey() + "' issue");
synchronizationInfo.addFailedReason(_(
"No worklogs found for \"{0}\" issue", issue.getKey()));
return;
}
List<WorkLogItemDTO> workLogItems = workLog.getWorklogs();
if (workLogItems.isEmpty()) {
jiraSyncInfo.addSyncFailedReason("No worklog items found for '"
+ issue.getKey() + "' issue");
synchronizationInfo.addFailedReason(_(
"No worklog items found for \"{0}\" issue",
issue.getKey()));
return;
}
@ -334,9 +397,10 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
} catch (DuplicateAdvanceAssignmentForOrderElementException e) {
// This could happen if a parent or child of the current
// OrderElement has an advance of type PERCENTAGE
jiraSyncInfo
.addSyncFailedReason("Duplicate value AdvanceAssignment for order element of '"
+ orderElement.getCode() + "'");
synchronizationInfo
.addFailedReason(_(
"Duplicate value AdvanceAssignment for order element of \"{0}\"",
orderElement.getCode()));
return;
}
}
@ -393,8 +457,109 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
}
@Override
public JiraSyncInfo getJiraSyncInfo() {
return jiraSyncInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
/**
* returns JIRA connector
*/
private Connector getJiraConnector() {
return connectorDAO.findUniqueByName(PredefinedConnectors.JIRA
.getName());
}
@Override
@Transactional
public void saveSyncInfo(final String key, final Order order) {
adHocTransactionService
.runOnAnotherTransaction(new IOnTransaction<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;
import static org.libreplan.web.I18nHelper._;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.hibernate.NonUniqueResultException;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.JiraConfiguration;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.orders.daos.IOrderSyncInfoDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.business.resources.daos.IWorkerDAO;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.daos.IWorkReportTypeDAO;
import org.libreplan.business.workreports.entities.PredefinedWorkReportTypes;
import org.libreplan.business.workreports.entities.WorkReport;
@ -63,7 +70,7 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private JiraSyncInfo jiraSyncInfo;
private SynchronizationInfo synchronizationInfo;
private List<Worker> workers;
@ -80,9 +87,6 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IWorkReportModel workReportModel;
@ -90,50 +94,69 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private ITypeOfWorkHoursDAO typeOfWorkHoursDAO;
@Autowired
private IConfigurationDAO configurationDAO;
private IConnectorDAO connectorDAO;
@Autowired
private IOrderSyncInfoDAO orderSyncInfoDAO;
@Autowired
private IAdHocTransactionService adHocTransactionService;
@Override
@Transactional
public void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) {
jiraSyncInfo = new JiraSyncInfo();
public void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) throws ConnectorException {
synchronizationInfo = new SynchronizationInfo(_("Synchronization"));
workReportType = getJiraTimesheetsWorkReportType();
typeOfWorkHours = getTypeOfWorkHours();
workers = getWorkers();
if (workers == null && workers.isEmpty()) {
jiraSyncInfo.addSyncFailedReason("No workers found");
synchronizationInfo.addFailedReason(_("No workers found"));
return;
}
String code = order.getCode() + "-" + order.getImportedLabel();
OrderSyncInfo orderSyncInfo = orderSyncInfoDAO
.findLastSynchronizedInfoByOrderAndConnectorName(order,
PredefinedConnectors.JIRA.getName());
if (orderSyncInfo == null) {
synchronizationInfo.addFailedReason(_(
"Order \"{0}\" not found. Order probalbly not synchronized",
order.getName()));
return;
}
if (StringUtils.isBlank(orderSyncInfo.getKey())) {
synchronizationInfo.addFailedReason(_(
"Key for Order \"{0}\" is empty",
order.getName()));
return;
}
String code = order.getCode() + "-" + orderSyncInfo.getKey();
WorkReport workReport = updateOrCreateWorkReport(code);
for (IssueDTO issue : issues) {
WorkLogDTO worklog = issue.getFields().getWorklog();
if (worklog == null) {
jiraSyncInfo.addSyncFailedReason("No worklogs found for '"
+ issue.getKey() + "'");
synchronizationInfo.addFailedReason(_(
"No worklogs found for \"{0}\" key", issue.getKey()));
} else {
List<WorkLogItemDTO> workLogItems = worklog.getWorklogs();
if (workLogItems == null || workLogItems.isEmpty()) {
jiraSyncInfo
.addSyncFailedReason("No worklog items found for '"
+ issue.getKey() + "' issue");
synchronizationInfo.addFailedReason(_(
"No worklog items found for \"{0}\" issue",
issue.getKey()));
} else {
String codeOrderElement = JiraConfiguration.CODE_PREFIX
String codeOrderElement = PredefinedConnectorProperties.JIRA_CODE_PREFIX
+ order.getCode() + "-" + issue.getKey();
OrderElement orderElement = order.getOrderElement(codeOrderElement);
if (orderElement == null) {
jiraSyncInfo.addSyncFailedReason("Order element("
+ code + ") not found");
synchronizationInfo.addFailedReason(_(
"Order element \"{0}\" not found", code));
} else {
updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement,
workLogItems);
@ -291,10 +314,31 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
* Returns {@link TypeOfWorkHours} configured for JIRA connector
*
* @return TypeOfWorkHours for JIRA connector
* @throws ConnectorException
*/
private TypeOfWorkHours getTypeOfWorkHours() {
return configurationDAO.getConfiguration().getJiraConfiguration()
.getJiraConnectorTypeOfWorkHours();
private TypeOfWorkHours getTypeOfWorkHours() throws ConnectorException {
Connector connector = connectorDAO
.findUniqueByName(PredefinedConnectors.JIRA.getName());
if (connector == null) {
throw new ConnectorException(_("JIRA connector not found"));
}
TypeOfWorkHours typeOfWorkHours;
String name = connector.getPropertiesAsMap().get(
PredefinedConnectorProperties.JIRA_HOURS_TYPE);
if (StringUtils.isBlank(name)) {
throw new ConnectorException(
_("Hours type should not be empty to synchronine timesheets"));
}
try {
typeOfWorkHours = typeOfWorkHoursDAO.findUniqueByName(name);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return typeOfWorkHours;
}
/**
@ -336,13 +380,13 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
return worker;
}
}
jiraSyncInfo.addSyncFailedReason("Worker('" + nif + "') not found");
synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found", nif));
return null;
}
@Override
public JiraSyncInfo getJiraSyncInfo() {
return jiraSyncInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
}

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;
/**
* Keeps track the synchronization info.
* Keeps track the success/failure of synchronization process
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JiraSyncInfo {
private List<String> syncFailedReasons = new ArrayList<String>();
public class SynchronizationInfo {
/**
* Add the specified <code>reason</code> to syncFailedReasons list
*
* @param reason
* reason why synchronizition failed
* The action, a unique key for example synchronization, import or export
* etc action
*/
public void addSyncFailedReason(String reason) {
syncFailedReasons.add(reason);
private String action;
/**
* Holds failed reasons
*/
private List<String> failedReasons = new ArrayList<String>();
public SynchronizationInfo(String action) {
this.action = action;
}
/**
* Is synchronization successful
*
* @return
* Returns the action
*/
public boolean isSyncSuccessful() {
return syncFailedReasons.isEmpty();
public String getAction() {
return action;
}
/**
* Adds the specified <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
*/
public List<String> getSyncFailedReasons() {
return Collections.unmodifiableList(syncFailedReasons);
public List<String> getFailedReasons() {
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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.MediaType;
@ -42,16 +43,20 @@ import org.apache.commons.logging.LogFactory;
import org.apache.cxf.jaxrs.client.WebClient;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.entities.Configuration;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorProperty;
import org.libreplan.business.common.entities.EntityNameEnum;
import org.libreplan.business.common.entities.EntitySequence;
import org.libreplan.business.common.entities.JiraConfiguration;
import org.libreplan.business.common.entities.LDAPConfiguration;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.entities.ProgressType;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.importers.JiraRESTClient;
import org.libreplan.importers.TimSoapClient;
import org.libreplan.web.common.components.bandboxsearch.BandboxSearch;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
@ -83,6 +88,7 @@ import org.zkoss.zul.Rows;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.api.Window;
import org.zkoss.zul.impl.InputElement;
/**
* Controller for {@link Configuration} entity.
@ -125,6 +131,12 @@ public class ConfigurationController extends GenericForwardComposer {
private Radiogroup strategy;
private Combobox connectorCombo;
private Grid connectorPropertriesGrid;
private Connector selectedConnector;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
@ -215,8 +227,16 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.confirm();
configurationModel.init();
messages.showMessage(Level.INFO, _("Changes saved"));
if (getSelectedConnector() != null
&& !configurationModel
.scheduleOrUnscheduleJobs(getSelectedConnector())) {
messages.showMessage(
Level.ERROR,
_("Scheduling or unscheduling of jobs for this connector is not completed"));
}
reloadWindow();
reloadEntitySequences();
reloadConnectors();
} catch (ValidationException e) {
messages.showInvalidValues(e);
} catch (ConcurrentModificationException e) {
@ -224,6 +244,7 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.init();
reloadWindow();
reloadEntitySequences();
reloadConnectors();
}
}
}
@ -233,6 +254,7 @@ public class ConfigurationController extends GenericForwardComposer {
messages.showMessage(Level.INFO, _("Changes have been canceled"));
reloadWindow();
reloadEntitySequences();
reloadConnectors();
}
public void testLDAPConnection() {
@ -267,22 +289,71 @@ public class ConfigurationController extends GenericForwardComposer {
}
/**
* tests jira connection
* Tests connection
*/
public void testJiraConnection() {
public void testConnection() {
if (selectedConnector == null) {
messages.showMessage(Level.ERROR,
_("Please select a connector to test it"));
return;
}
JiraConfiguration jiraConfiguration = configurationModel
.getJiraConfiguration();
Map<String, String> properties = selectedConnector.getPropertiesAsMap();
String url = properties.get(PredefinedConnectorProperties.SERVER_URL);
String username = properties
.get(PredefinedConnectorProperties.USERNAME);
String password = properties
.get(PredefinedConnectorProperties.PASSWORD);
if (selectedConnector.getName().equals(
PredefinedConnectors.TIM.getName())) {
testTimConnection(url, username, password);
} else if (selectedConnector.getName().equals(
PredefinedConnectors.JIRA.getName())) {
testJiraConnection(url, username, password);
} else {
throw new RuntimeException("Unknown connector");
}
}
/**
* Test tim connection
*
* @param url
* the url of the server
* @param username
* the username
* @param password
* the password
*/
private void testTimConnection(String url, String username, String password) {
if (TimSoapClient.checkAuthorization(url, username, password)) {
messages.showMessage(Level.INFO, _("Tim connection was successful"));
} else {
messages.showMessage(Level.ERROR, _("Cannot connet to Tim server"));
}
}
/**
* Test JIRA connection
*
* @param url
* the url
* @param username
* the username
* @param password
* the password
*/
private void testJiraConnection(String url, String username, String password) {
try {
WebClient client = WebClient.create(jiraConfiguration.getJiraUrl());
WebClient client = WebClient.create(url);
client.path(JiraRESTClient.PATH_AUTH_SESSION).accept(
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML);
org.libreplan.ws.common.impl.Util.addAuthorizationHeader(client,
jiraConfiguration.getJiraUserId(),
jiraConfiguration.getJiraPassword());
username, password);
Response response = client.get();
@ -290,17 +361,16 @@ public class ConfigurationController extends GenericForwardComposer {
messages.showMessage(Level.INFO,
_("JIRA connection was successful"));
} else {
LOG.info("Status code: " + response.getStatus());
LOG.error("Status code: " + response.getStatus());
messages.showMessage(Level.ERROR,
_("Cannot connect to JIRA server"));
}
} catch (Exception e) {
LOG.info(e);
LOG.error(e);
messages.showMessage(Level.ERROR,
_("Cannot connect to JIRA server"));
}
}
private boolean checkValidEntitySequenceRows() {
@ -346,6 +416,14 @@ public class ConfigurationController extends GenericForwardComposer {
entitySequencesGrid.invalidate();
}
private void reloadConnectors() {
selectedConnector = configurationModel
.getConnectorByName(selectedConnector != null ? selectedConnector
.getName() : null);
Util.reloadBindings(connectorCombo);
Util.reloadBindings(connectorPropertriesGrid);
}
public String getCompanyCode() {
return configurationModel.getCompanyCode();
}
@ -818,14 +896,6 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.setLdapConfiguration(ldapConfiguration);
}
public JiraConfiguration getJiraConfiguration() {
return configurationModel.getJiraConfiguration();
}
public void setJiraConfiguration(JiraConfiguration jiraConfiguration) {
configurationModel.setJiraConfiguration(jiraConfiguration);
}
public RowRenderer getAllUserRolesRenderer() {
return new RowRenderer() {
@Override
@ -976,12 +1046,105 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.setSecondsPlanningWarning(secondsPlanningWarning);
}
public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() {
return configurationModel.getJiraConnectorTypeOfWorkHours();
public List<Connector> getConnectors() {
return configurationModel.getConnectors();
}
public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) {
configurationModel.setJiraConnectorTypeOfWorkHours(typeOfWorkHours);
public Connector getSelectedConnector() {
return selectedConnector;
}
public void setSelectedConnector(Connector connector) {
selectedConnector = connector;
Util.reloadBindings(connectorPropertriesGrid);
}
public List<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.Collection;
import java.util.Collections;
import java.util.Currency;
import java.util.HashMap;
import java.util.HashSet;
@ -38,11 +39,12 @@ import org.apache.commons.lang.StringUtils;
import org.libreplan.business.calendars.daos.IBaseCalendarDAO;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.daos.IEntitySequenceDAO;
import org.libreplan.business.common.entities.Configuration;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.EntityNameEnum;
import org.libreplan.business.common.entities.EntitySequence;
import org.libreplan.business.common.entities.JiraConfiguration;
import org.libreplan.business.common.entities.LDAPConfiguration;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.common.entities.ProgressType;
@ -76,6 +78,8 @@ public class ConfigurationModel implements IConfigurationModel {
private static Map<String, String> currencies = getAllCurrencies();
private List<Connector> connectors;
@Autowired
private IConfigurationDAO configurationDAO;
@ -88,6 +92,12 @@ public class ConfigurationModel implements IConfigurationModel {
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IConnectorDAO connectorDAO;
@Autowired
private IJobSchedulerModel jobSchedulerModel;
@Override
@Transactional(readOnly = true)
public List<BaseCalendar> getCalendars() {
@ -107,6 +117,8 @@ public class ConfigurationModel implements IConfigurationModel {
public void init() {
this.configuration = getCurrentConfiguration();
initEntitySequences();
initLdapConfiguration();
initConnectorConfiguration();
}
private void initEntitySequences() {
@ -120,6 +132,23 @@ public class ConfigurationModel implements IConfigurationModel {
}
}
private void initLdapConfiguration() {
if (null == configuration.getLdapConfiguration()) {
configuration.setLdapConfiguration(LDAPConfiguration.create());
}
}
private void initConnectorConfiguration() {
connectors = connectorDAO.getAll();
forceLoadConnectors();
}
private void forceLoadConnectors() {
for (Connector connector : connectors) {
connector.getProperties().size();
}
}
private Configuration getCurrentConfiguration() {
Configuration configuration = configurationDAO.getConfiguration();
if (configuration == null) {
@ -132,8 +161,6 @@ public class ConfigurationModel implements IConfigurationModel {
private void forceLoad(Configuration configuration) {
forceLoad(configuration.getDefaultCalendar());
forceLoad(configuration.getPersonalTimesheetsTypeOfWorkHours());
forceLoad(configuration.getJiraConfiguration()
.getJiraConnectorTypeOfWorkHours());
}
private void forceLoad(BaseCalendar calendar) {
@ -160,6 +187,7 @@ public class ConfigurationModel implements IConfigurationModel {
public void confirm() {
checkEntitySequences();
configurationDAO.save(configuration);
saveConnectors();
try {
storeAndRemoveEntitySequences();
} catch (IllegalStateException e) {
@ -663,36 +691,34 @@ public class ConfigurationModel implements IConfigurationModel {
configuration.setSecondsPlanningWarning(secondsPlanningWarning);
}
@Override
public void setJiraConfiguration(JiraConfiguration jiraConfiguration) {
configuration.setJiraConfiguration(jiraConfiguration);
private void saveConnectors() {
for (Connector connector : connectors) {
connectorDAO.save(connector);
}
}
@Override
public JiraConfiguration getJiraConfiguration() {
return configuration.getJiraConfiguration();
public List<Connector> getConnectors() {
return Collections.unmodifiableList(connectors);
}
@Override
public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() {
JiraConfiguration jiraConfiguration = configuration
.getJiraConfiguration();
if (jiraConfiguration != null) {
return jiraConfiguration.getJiraConnectorTypeOfWorkHours();
public Connector getConnectorByName(String name) {
if (name == null || connectors == null) {
return null;
}
for (Connector connector : connectors) {
if (connector.getName().equals(name)) {
return connector;
}
}
return null;
}
@Override
public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) {
if (configuration != null) {
JiraConfiguration jiraConfiguration = configuration
.getJiraConfiguration();
if (jiraConfiguration != null) {
jiraConfiguration
.setJiraConnectorTypeOfWorkHours(typeOfWorkHours);
}
}
public boolean scheduleOrUnscheduleJobs(Connector connector) {
return jobSchedulerModel.scheduleOrUnscheduleJobs(connector);
}
}

View file

@ -414,6 +414,13 @@ public class CustomMenuController extends Div implements IMenuItemsRegister {
"/profiles/profiles.zul",
"13-usuarios.html#administraci-n-de-perfiles"));
}
if (SecurityUtils
.isSuperuserOrUserInRoles(UserRole.ROLE_JOB_SCHEDULING)) {
configurationItems.add(subItem(_("Job Scheduling"),
"/common/job_scheduling.zul",
"16-ldap-authentication.html"));
}
if (!configurationItems.isEmpty()) {
topItem(_("Configuration"), "/common/configuration.zul", "",
configurationItems);

View file

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

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.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -161,10 +162,10 @@ public class DashboardController extends GenericForwardComposer {
if ((lblAbsolute != null) && (absoluteMargin != null)) {
lblAbsolute
.setValue(String
.format(_("There is a margin of %d days with the project global deadline (%.2f %%)."),
absoluteMargin + 0,
relativeMargin.doubleValue() * 100));
.setValue(_(
"There is a margin of {0} days with the project global deadline ({1}%).",
absoluteMargin, (new DecimalFormat("#.##"))
.format(relativeMargin.doubleValue() * 100)));
} else {
lblAbsolute.setValue(_("No project deadline defined"));
}

View file

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

View file

@ -156,6 +156,4 @@ public interface IOrderModel extends IIntegrationEntityModel {
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.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.Resource;
import javax.ws.rs.WebApplicationException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.externalcompanies.entities.DeadlineCommunication;
import org.libreplan.business.externalcompanies.entities.DeliverDateComparator;
@ -58,10 +55,6 @@ import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.templates.entities.OrderTemplate;
import org.libreplan.business.users.entities.User;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.importers.IJiraOrderElementSynchronizer;
import org.libreplan.importers.IJiraTimesheetSynchronizer;
import org.libreplan.importers.JiraSyncInfo;
import org.libreplan.importers.jira.IssueDTO;
import org.libreplan.web.common.ConfirmCloseUtil;
import org.libreplan.web.common.FilterUtils;
import org.libreplan.web.common.IMessagesForUser;
@ -92,7 +85,6 @@ import org.zkoss.ganttz.util.LongOperationFeedback;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.SuspendNotAllowedException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
@ -110,9 +102,7 @@ import org.zkoss.zul.Datebox;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Hbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Popup;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Rows;
@ -202,6 +192,10 @@ public class OrderCRUDController extends GenericForwardComposer {
private ProjectDetailsController projectDetailsController;
private JiraSynchronizationController jiraSynchronizationController;
private TimSynchronizationController timSynchronizationController;
@Autowired
private IOrderDAO orderDAO;
@ -209,16 +203,6 @@ public class OrderCRUDController extends GenericForwardComposer {
private EndDatesRenderer endDatesRenderer = new EndDatesRenderer();
@Autowired
private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer;
@Autowired
private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer;
@Autowired
private IConfigurationDAO configurationDAO;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
@ -821,7 +805,7 @@ public class OrderCRUDController extends GenericForwardComposer {
saveAndContinue(true);
}
private void saveAndContinue(boolean showSaveMessage) {
protected void saveAndContinue(boolean showSaveMessage) {
Order order = orderModel.getOrder();
final boolean isNewObject = order.isNewObject();
@ -930,11 +914,11 @@ public class OrderCRUDController extends GenericForwardComposer {
}
}
private Tab getCurrentTab() {
protected Tab getCurrentTab() {
return selectedTab;
}
private void selectTab(String str) {
protected void selectTab(String str) {
Tab tab = (Tab) editWindow.getFellowIfAny(str);
if (tab != null) {
tab.setSelected(true);
@ -1082,6 +1066,12 @@ public class OrderCRUDController extends GenericForwardComposer {
}
public void initEdit(Order order) {
checkUserCanRead(order);
orderModel.initEdit(order, getDesktop());
prepareEditWindow(_("Edit project"));
}
public void checkUserCanRead(Order order) {
if (!orderModel.userCanRead(order, SecurityUtils.getSessionUserLoginName())) {
try {
Messagebox.show(_("Sorry, you do not have permissions to access this project"),
@ -1090,9 +1080,6 @@ public class OrderCRUDController extends GenericForwardComposer {
throw new RuntimeException(e);
}
}
orderModel.initEdit(order, getDesktop());
prepareEditWindow(_("Edit project"));
}
public IOrderModel getOrderModel() {
@ -1169,6 +1156,8 @@ public class OrderCRUDController extends GenericForwardComposer {
initializeCustomerComponent();
reloadOrderDetailsTab();
orderDatesHandler.chooseCurrentSchedulingMode();
setupJiraSynchronizationController();
setupTimSynchronizationController();
}
public void reloadOrderDetailsTab() {
@ -1876,167 +1865,32 @@ public class OrderCRUDController extends GenericForwardComposer {
loadLabels();
}
private Popup jirasyncPopup;
private Button startJiraSyncButton, cancelJiraSyncButton, syncWithJiraButton;
private Combobox comboJiraLabel;
public boolean isJiraDeactivated() {
return !configurationDAO.getConfigurationWithReadOnlyTransaction()
.getJiraConfiguration().isJiraActivated();
}
public void syncWithJira() {
try {
List<String> items = jiraOrderElementSynchronizer.getAllJiraLabels();
Textbox txtImportedLabel = (Textbox) editWindow
.getFellowIfAny("txtImportedLabel");
if (!(txtImportedLabel.getText()).isEmpty()) {
startSyncWithJira(txtImportedLabel.getText());
return;
}
setupJiraSyncPopup(editWindow, new SimpleListModelExt(items));
syncWithJiraButton = (Button) getCurrentTab().getFellow(
"syncWithJiraButton");
jirasyncPopup.open(syncWithJiraButton, "before_start");
} catch (WebApplicationException e) {
LOG.info(e);
messagesForUser.showMessage(Level.ERROR,
_("Cannot connect to JIRA server"));
/**
* Setup the connector, JiraSynchronization controller
*/
public void setupJiraSynchronizationController() {
if (jiraSynchronizationController == null) {
jiraSynchronizationController = new JiraSynchronizationController();
}
}
public void startSyncWithJira(String label) {
try {
Order order = getOrder();
List<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) {
jiraSynchronizationController.doAfterCompose(editWindow);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void setupJiraSyncPopup(Component comp, ListModel model) {
startJiraSyncButton = (Button) comp.getFellow("startJiraSyncButton");
startJiraSyncButton.setLabel(_("Start sync"));
startJiraSyncButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) {
startSyncWithJira(comboJiraLabel.getValue());
}
});
cancelJiraSyncButton = (Button) comp.getFellow("cancelJiraSyncButton");
cancelJiraSyncButton.setLabel(_("Cancel"));
cancelJiraSyncButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) {
jirasyncPopup.close();
}
});
comboJiraLabel = (Combobox) comp.getFellowIfAny("comboJiraLabel");
comboJiraLabel.setModel(model);
jirasyncPopup = (Popup) comp.getFellow("jirasyncPopup");
}
/**
* This class provides case insensitive search for the {@link Combobox}.
* Setup the connector, TimSynchronization controller
*/
private class SimpleListModelExt extends SimpleListModel {
public SimpleListModelExt(List data) {
super(data);
public void setupTimSynchronizationController() {
if (timSynchronizationController == null) {
timSynchronizationController = new TimSynchronizationController();
}
public ListModel getSubModel(Object value, int nRows) {
final String idx = value == null ? "" : objectToString(value);
if (nRows < 0) {
nRows = 10;
}
final LinkedList data = new LinkedList();
for (int i = 0; i < getSize(); i++) {
if (idx.equals("")
|| entryMatchesText(getElementAt(i).toString(), idx)) {
data.add(getElementAt(i));
if (--nRows <= 0) {
break;
}
}
}
return new SimpleListModelExt(data);
try {
timSynchronizationController.doAfterCompose(editWindow);
} catch (Exception e) {
throw new RuntimeException(e);
}
public boolean entryMatchesText(String entry, String text) {
return entry.toLowerCase().contains(text.toLowerCase());
}
}
public boolean isJiraActivated() {
return orderModel.isJiraActivated();
}
}

View file

@ -37,9 +37,11 @@ import java.util.logging.Filter;
import javax.annotation.Resource;
import org.apache.commons.lang.StringUtils;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.EntitySequence;
import org.libreplan.business.common.entities.JiraConfiguration;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine;
@ -126,7 +128,7 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
private Popup filterOptionsPopup;
@Autowired
private IConfigurationDAO configurationDAO;
private IConnectorDAO connectorDAO;
public List<org.libreplan.business.labels.entities.Label> getLabels() {
return orderModel.getLabels();
@ -510,9 +512,17 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
String code = orderElement.getCode();
A hyperlink = new A(code);
String jiraUrl = configurationDAO.getConfigurationWithReadOnlyTransaction().getJiraConfiguration().getJiraUrl();
Connector connector = connectorDAO
.findUniqueByName(PredefinedConnectors.JIRA.getName());
if (connector == null) {
return;
}
String codeWithoutPrefix = StringUtils.removeStart(code, JiraConfiguration.CODE_PREFIX);
String jiraUrl = connector.getPropertiesAsMap().get(
PredefinedConnectorProperties.SERVER_URL);
String codeWithoutPrefix = StringUtils.removeStart(code,
PredefinedConnectorProperties.JIRA_CODE_PREFIX);
codeWithoutPrefix = StringUtils.removeStart(codeWithoutPrefix,
orderElement.getOrder().getCode()
+ EntitySequence.CODE_SEPARATOR_CHILDREN);

View file

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

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")));
if (mode.isOf(ModeType.ORDER)) {
orderPlanningController.getOrderCRUDController()
.checkUserCanRead(order);
Label nameLabel = new Label(order.getName());
nameLabel.setTooltiptext(order.getName() + "."
+ order.getDescription());

View file

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

View file

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

View file

@ -5203,7 +5203,7 @@ msgstr ""
#: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165
msgid ""
"There is a margin of %d days with the project global deadline (%.2f %%)."
"There is a margin of {0} days with the project global deadline ({1}%)."
msgstr ""
#: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91

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
# Desenvolvemento Tecnolóxico de Galicia
# Copyright (C) 2010-2012 Igalia, S.L.
@ -72,7 +72,7 @@ msgid "Create Virtual Worker"
msgstr "创建虚拟工作人员"
#: libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java:862
#: libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeComponent.java:105
#: libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeComponent.java:104
#: libreplan-webapp/src/main/webapp/labels/_editLabelType.zul:81
#: libreplan-webapp/src/main/webapp/labels/_listLabelTypes.zul:28
#: libreplan-webapp/src/main/webapp/advance/_listAdvanceTypes.zul:30
@ -5156,8 +5156,8 @@ msgstr "阅读不仅是因为他们的报告由分包商公司的分包商值。
#: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165
msgid ""
"There is a margin of %d days with the project global deadline (%.2f %%)."
msgstr "总项目的截止日期(%.2f %%)前有%d天的余量。"
"There is a margin of {0} days with the project global deadline ({1}%)."
msgstr "总项目的截止日期({1}%)前有{0}天的余量。"
#: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250
msgid "CV"

View file

@ -49,6 +49,16 @@
class="org.libreplan.web.scenarios.CurrentUserScenarioAwareManager"
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"/>
<!-- 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:_('Entity sequences')}" />
<tab label="${i18n:_('LDAP configuration')}" />
<tab label="${i18n:_('JIRA connector')}" />
<tab label="${i18n:_('Connectors')}" />
</tabs>
<tabpanels>
<tabpanel id="panelConfiguration">
@ -414,47 +414,35 @@
</vbox>
</groupbox>
</tabpanel>
<tabpanel id="panelJiraConfiguration">
<grid>
<columns>
<column width="200px" />
<column />
</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>
<separator />
<tabpanel id="panelConnectors">
<groupbox style="margin-top: 5px" closable="false">
<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>
<column label="${i18n:_('Name')}" width="200px"/>
<column label="${i18n:_('Value')}" />
</columns>
</grid>
</vbox>
<separator />
<button label="${i18n:_('Test connection')}"
onClick="configurationController.testJiraConnection()" />
<separator />
onClick="configurationController.testConnection()" />
</groupbox>
</tabpanel>
</tabpanels>
</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