/*
* 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());
}
}
}