/* * This file is part of LibrePlan * * 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. * * 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.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.InputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.IOException; import java.util.List; import java.util.ConcurrentModificationException; import java.util.Properties; import java.util.ArrayList; import java.util.Set; import java.util.Map; import java.util.Collections; import java.util.HashSet; import java.util.Arrays; import javax.mail.Session; import javax.mail.Transport; import javax.mail.AuthenticationFailedException; import javax.mail.MessagingException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; 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.daos.IConfigurationDAO; 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.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.libreplan.web.security.SecurityUtils; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.support.DefaultDirObjectFactory; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.web.context.ContextLoaderListener; import org.zkoss.util.media.Media; import org.zkoss.zk.ui.Component; 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.zk.ui.event.SelectEvent; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zkplus.spring.SpringUtil; import org.zkoss.zul.ListitemRenderer; import org.zkoss.zul.Listbox; import org.zkoss.zul.Grid; import org.zkoss.zul.Combobox; import org.zkoss.zul.Intbox; import org.zkoss.zul.Textbox; import org.zkoss.zul.Radiogroup; import org.zkoss.zul.Listitem; import org.zkoss.zul.Row; import org.zkoss.zul.Constraint; import org.zkoss.zul.RowRenderer; import org.zkoss.zul.Comboitem; import org.zkoss.zul.Radio; import org.zkoss.zul.Button; import org.zkoss.zul.Label; import org.zkoss.zul.Rows; import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Messagebox; import org.zkoss.zul.Window; import org.zkoss.zul.impl.InputElement; /** * Controller for {@link Configuration} entity. * * @author Manuel Rego Casasnovas <mrego@igalia.com> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> * @author Cristina Alavarino Perez <cristina.alvarino@comtecsf.es> * @author Ignacio Diaz Teijido <ignacio.diaz@comtecsf.es> * @author Vova Perebykivskyi <vova@libreplan-enterprise.com> */ public class ConfigurationController extends GenericForwardComposer { private static final Log LOG = LogFactory.getLog(ConfigurationController.class); private final ProgressTypeRenderer progressTypeRenderer = new ProgressTypeRenderer(); private Window configurationWindow; private BandboxSearch defaultCalendarBandboxSearch; private Listbox lbTypeProgress; private IConfigurationModel configurationModel; private IConfigurationDAO configurationDAO; private IMessagesForUser messages; private Component messagesContainer; private Grid entitySequencesGrid; private Combobox entityCombo; private Intbox numDigitBox; private Textbox prefixBox; private Textbox ldapGroupPath; private Radiogroup strategy; private Combobox connectorCombo; private Grid connectorPropertriesGrid; private Connector selectedConnector; private Combobox protocolsCombobox; private Textbox emailUsernameTextbox; private Textbox emailPasswordTextbox; private Textbox emailSenderTextbox; private Textbox companyLogoURL; private String STARTTLS_PROTOCOL = "STARTTLS"; private String LOGO_PREVIEW_COMPONENT = "logoPreview"; public ConfigurationController() { } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); injectsObjects(); comp.setAttribute("configurationController", this, true); configurationModel.init(); defaultCalendarBandboxSearch.setListboxEventListener(Events.ON_SELECT, event -> { Listitem selectedItem = (Listitem) ((SelectEvent) event).getSelectedItems().iterator().next(); setDefaultCalendar(selectedItem.getValue()); }); initializeProgressTypeList(); messages = new MessagesForUser(messagesContainer); reloadEntitySequences(); loadRoleStrategyRows(); } private void injectsObjects() { if ( configurationModel == null ) { configurationModel = (IConfigurationModel) SpringUtil.getBean("configurationModel"); } if ( configurationDAO == null ) { configurationDAO = (IConfigurationDAO) SpringUtil.getBean("configurationDAO"); } } /** * Used in configuration.zul * Should be public! */ public void changeRoleStrategy() { this.getLdapConfiguration().setLdapGroupStrategy("group".equals(strategy.getSelectedItem().getValue())); loadRoleStrategyRows(); } private void loadRoleStrategyRows() { if ( getLdapConfiguration().getLdapGroupStrategy() ) { strategy.setSelectedIndex(0); ldapGroupPath.setDisabled(false); } else { strategy.setSelectedIndex(1); ldapGroupPath.setDisabled(true); } } private void initializeProgressTypeList() { lbTypeProgress.addEventListener(Events.ON_SELECT, new EventListener() { @Override public void onEvent(Event event) { Listitem selectedItem = getSelectedItem((SelectEvent) event); if ( selectedItem != null ) { ProgressType progressType = selectedItem.getValue(); configurationModel.setProgressType(progressType); } } private Listitem getSelectedItem(SelectEvent event) { final Set<Listitem> selectedItems = event.getSelectedItems(); return selectedItems.iterator().next(); } }); } /** * Used in configuration.zul * Should be public! */ public List<ProgressType> getProgressTypes() { return configurationModel.getProgressTypes(); } /** * Used in configuration.zul * Should be public! */ public ProgressType getSelectedProgressType() { return configurationModel.getProgressType(); } /** * Used in configuration.zul * Should be public! */ public void setSelectedProgressType(ProgressType progressType) { configurationModel.setProgressType(progressType); } public List<BaseCalendar> getCalendars() { return configurationModel.getCalendars(); } public BaseCalendar getDefaultCalendar() { return configurationModel.getDefaultCalendar(); } public void setDefaultCalendar(BaseCalendar calendar) { configurationModel.setDefaultCalendar(calendar); } public void save() throws InterruptedException { if ( getSelectedConnector() != null && "E-mail".equals(getSelectedConnector().getName()) && !areEmailFieldsValid() ) { messages.clearMessages(); messages.showMessage(Level.ERROR, _("Check all fields")); } else { ConstraintChecker.isValid(configurationWindow); if ( checkValidEntitySequenceRows() ) { try { configurationModel.confirm(); configurationModel.init(); messages.showMessage(Level.INFO, _("Changes saved")); // Send data to server if ( !SecurityUtils.isGatheredStatsAlreadySent && configurationDAO.getConfigurationWithReadOnlyTransaction().isAllowedToGatherUsageStatsEnabled() ) { sendDataToServer(); } 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) { messages.showMessage(Level.ERROR, e.getMessage()); configurationModel.init(); reloadWindow(); reloadEntitySequences(); reloadConnectors(); } } } } private void sendDataToServer() { GatheredUsageStats gatheredUsageStats = new GatheredUsageStats(); gatheredUsageStats.sendGatheredUsageStatsToServer(); SecurityUtils.isGatheredStatsAlreadySent = true; } public void cancel() throws InterruptedException { configurationModel.cancel(); messages.clearMessages(); messages.showMessage(Level.INFO, _("Changes have been canceled")); reloadWindow(); reloadEntitySequences(); reloadConnectors(); } /** * Used in configuration.zul * Should be public! */ public void testLDAPConnection() { LdapContextSource source = new LdapContextSource(); source.setUrl(configurationModel.getLdapConfiguration().getLdapHost() + ":" + configurationModel.getLdapConfiguration().getLdapPort()); source.setBase(configurationModel.getLdapConfiguration().getLdapBase()); source.setUserDn(configurationModel.getLdapConfiguration().getLdapUserDn()); source.setPassword(configurationModel.getLdapConfiguration().getLdapPassword()); source.setDirObjectFactory(DefaultDirObjectFactory.class); source.setPooled(false); try { source.afterPropertiesSet(); } catch (Exception e) { e.printStackTrace(); } LdapTemplate template = new LdapTemplate(source); try { /* TODO resolve deprecated */ template.authenticate( DistinguishedName.EMPTY_PATH, new EqualsFilter(configurationModel.getLdapConfiguration().getLdapUserId(), "test").toString(), "test"); messages.showMessage(Level.INFO, _("LDAP connection was successful")); } catch (Exception e) { LOG.info(e); messages.showMessage(Level.ERROR, _("Cannot connect to LDAP server")); } } /** * Tests connection. * * Used in configuration.zul * Should be public! */ public void testConnection() { if (selectedConnector == null) { messages.showMessage(Level.ERROR, _("Please select a connector to test it")); return; } 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 if( selectedConnector.getName().equals(PredefinedConnectors.EMAIL.getName()) ) { String host = properties.get(PredefinedConnectorProperties.HOST); username = properties.get(PredefinedConnectorProperties.EMAIL_USERNAME); password = properties.get(PredefinedConnectorProperties.EMAIL_PASSWORD); String port = properties.get(PredefinedConnectorProperties.PORT); testEmailConnection(host, port, 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(url); client.path(JiraRESTClient.PATH_AUTH_SESSION).accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML); org.libreplan.ws.common.impl.Util.addAuthorizationHeader(client, username, password); Response response = client.get(); if ( response.getStatus() == Status.OK.getStatusCode() ) { messages.showMessage(Level.INFO, _("JIRA connection was successful")); } else { LOG.error("Status code: " + response.getStatus()); messages.showMessage(Level.ERROR, _("Cannot connect to JIRA server")); } } catch (Exception e) { LOG.error(e); messages.showMessage(Level.ERROR, _("Cannot connect to JIRA server")); } } /** * Test E-mail connection. * * @param host * the host * @param port * the port * @param username * the username * @param password * the password */ private void testEmailConnection(String host, String port, String username, String password) { Properties props = System.getProperties(); Transport transport = null; try { if ("SMTP".equals(protocolsCombobox.getSelectedItem().getLabel())) { props.setProperty("mail.smtp.port", port); props.setProperty("mail.smtp.host", host); props.setProperty("mail.smtp.connectiontimeout", Integer.toString(3000)); Session session = Session.getInstance(props, null); transport = session.getTransport("smtp"); if ("".equals(username) && "".equals(password)) { transport.connect(); } } else if (STARTTLS_PROTOCOL.equals(protocolsCombobox.getSelectedItem().getLabel())) { props.setProperty("mail.smtps.port", port); props.setProperty("mail.smtps.host", host); props.setProperty("mail.smtps.connectiontimeout", Integer.toString(3000)); Session session = Session.getInstance(props, null); transport = session.getTransport("smtps"); if ( !"".equals(username) && password != null ) { transport.connect(host, username, password); } } messages.clearMessages(); if (transport != null) { if ( transport.isConnected() ) { messages.showMessage(Level.INFO, _("Connection successful!")); } else if ( !transport.isConnected() ) { messages.showMessage(Level.WARNING, _("Connection unsuccessful")); } } } catch (AuthenticationFailedException e) { messages.clearMessages(); messages.showMessage(Level.ERROR, _("Invalid credentials")); } catch (MessagingException e) { LOG.error(e); messages.clearMessages(); messages.showMessage(Level.ERROR, _("Cannot connect")); } catch (Exception e) { LOG.error(e); messages.clearMessages(); messages.showMessage(Level.ERROR, _("Failed to connect")); } } private boolean checkValidEntitySequenceRows() { Rows rows = entitySequencesGrid.getRows(); List<Row> listRows = rows.getChildren(); for (Row row : listRows) { EntitySequence seq = row.getValue(); if ( seq != null ) { Textbox prefixBox = (Textbox) row.getChildren().get(2); if ( !seq.isAlreadyInUse() ) { String errorMessage = this.validPrefix(seq, prefixBox.getValue()); if ( errorMessage != null ) { throw new WrongValueException(prefixBox, errorMessage); } } Intbox digitsBox = (Intbox) row.getChildren().get(3); try { if ( !seq.isAlreadyInUse() ) { seq.setNumberOfDigits(digitsBox.getValue()); } } catch (IllegalArgumentException e) { throw new WrongValueException( digitsBox, _("number of digits must be between {0} and {1}", EntitySequence.MIN_NUMBER_OF_DIGITS, EntitySequence.MAX_NUMBER_OF_DIGITS)); } } } return true; } private void reloadWindow() { Util.reloadBindings(configurationWindow); } private void reloadEntitySequences() { entitySequencesGrid.setModel(new SimpleListModel<>(getAllEntitySequences().toArray())); entitySequencesGrid.invalidate(); } private void reloadConnectors() { selectedConnector = configurationModel.getConnectorByName(selectedConnector != null ? selectedConnector.getName() : null); Util.reloadBindings(connectorCombo); Util.reloadBindings(connectorPropertriesGrid); } /** * Used in configuration.zul * Should be public! */ public String getCompanyCode() { return configurationModel.getCompanyCode(); } /** * Used in configuration.zul * Should be public! */ public void setCompanyCode(String companyCode) { configurationModel.setCompanyCode(companyCode); } /** * Used in configuration.zul * Should be public! */ public String getCompanyLogoURL() { return configurationModel.getCompanyLogoURL(); } /** * Used in configuration.zul * Should be public! */ public void setCompanyLogoURL(String companyLogoURL) { configurationModel.setCompanyLogoURL(companyLogoURL); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForCriterion() { return configurationModel.getGenerateCodeForCriterion(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForCriterion(Boolean generateCodeForCriterion) { configurationModel.setGenerateCodeForCriterion(generateCodeForCriterion); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForWorkReportType() { return configurationModel.getGenerateCodeForWorkReportType(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForWorkReportType(Boolean generateCodeForWorkReportType) { configurationModel.setGenerateCodeForWorkReportType(generateCodeForWorkReportType); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForCalendarExceptionType() { return configurationModel.getGenerateCodeForCalendarExceptionType(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForCalendarExceptionType(Boolean generateCodeForCalendarExceptionType) { configurationModel.setGenerateCodeForCalendarExceptionType(generateCodeForCalendarExceptionType); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForCostCategory() { return configurationModel.getGenerateCodeForCostCategory(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForCostCategory(Boolean generateCodeForCostCategory) { configurationModel.setGenerateCodeForCostCategory(generateCodeForCostCategory); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForLabel() { return configurationModel.getGenerateCodeForLabel(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForLabel(Boolean generateCodeForLabel) { configurationModel.setGenerateCodeForLabel(generateCodeForLabel); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForWorkReport() { return configurationModel.getGenerateCodeForWorkReport(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForWorkReport(Boolean generateCodeForWorkReport) { configurationModel.setGenerateCodeForWorkReport(generateCodeForWorkReport); } public Boolean getGenerateCodeForResources() { return configurationModel.getGenerateCodeForResources(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForResources(Boolean generateCodeForResources) { configurationModel.setGenerateCodeForResources(generateCodeForResources); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForTypesOfWorkHours() { return configurationModel.getGenerateCodeForTypesOfWorkHours(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForTypesOfWorkHours(Boolean generateCodeForTypesOfWorkHours) { configurationModel.setGenerateCodeForTypesOfWorkHours(generateCodeForTypesOfWorkHours); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForMaterialCategories() { return configurationModel.getGenerateCodeForMaterialCategories(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForMaterialCategories(Boolean generateCodeForMaterialCategories) { configurationModel.setGenerateCodeForMaterialCategories(generateCodeForMaterialCategories); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForExpenseSheets() { return configurationModel.getGenerateCodeForExpenseSheets(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForExpenseSheets(Boolean generateCodeForExpenseSheets) { configurationModel.setGenerateCodeForExpenseSheets(generateCodeForExpenseSheets); } /** * Used in configuration.zul * Should be public! */ public void reloadGeneralConfiguration() { reloadWindow(); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForUnitTypes() { return configurationModel.getGenerateCodeForUnitTypes(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForUnitTypes(Boolean generateCodeForUnitTypes) { configurationModel.setGenerateCodeForUnitTypes(generateCodeForUnitTypes); } /** * Used in configuration.zul * Should be public! */ public Boolean getGenerateCodeForBaseCalendars() { return configurationModel.getGenerateCodeForBaseCalendars(); } /** * Used in configuration.zul * Should be public! */ public void setGenerateCodeForBaseCalendars(Boolean generateCodeForBaseCalendars) { configurationModel.setGenerateCodeForBaseCalendars(generateCodeForBaseCalendars); } /** * Used in configuration.zul * Should be public! */ public Boolean isAutocompleteLogin() { return configurationModel.isAutocompleteLogin(); } /** * Used in configuration.zul * Should be public! */ public void setAutocompleteLogin(Boolean autocompleteLogin) { configurationModel.setAutocompleteLogin(autocompleteLogin); } public void removeEntitySequence(EntitySequence entitySequence) { try { configurationModel.removeEntitySequence(entitySequence); } catch (IllegalArgumentException e) { messages.showMessage(Level.ERROR, e.getMessage()); } reloadEntitySequences(); } /** * Used in configuration.zul * Should be public! */ public void setMonteCarloMethodTabVisible(Boolean expandResourceLoadViewCharts) { configurationModel.setMonteCarloMethodTabVisible(expandResourceLoadViewCharts); } /** * Used in configuration.zul * Should be public! */ public Boolean isMonteCarloMethodTabVisible() { return configurationModel.isMonteCarloMethodTabVisible(); } /** * Used in configuration.zul * Should be public! */ public ProgressTypeRenderer getProgressTypeRenderer() { return progressTypeRenderer; } private static class ProgressTypeRenderer implements ListitemRenderer { @Override public void render(Listitem listitem, Object o, int i) throws Exception { ProgressType progressType = (ProgressType) o; listitem.setLabel(_(progressType.getValue())); listitem.setValue(progressType); } } private class EntitySequenceGroupRenderer implements RowRenderer { @Override public void render(Row row, Object o, int i) throws Exception { EntitySequence entitySequence = (EntitySequence) o; final EntityNameEnum entityName = entitySequence.getEntityName(); row.setValue(entityName); row.appendChild(new Label(_("{0} sequences", entityName.getDescription()))); row.setValue(entitySequence); appendActiveRadiobox(row, entitySequence); appendPrefixTextbox(row, entitySequence); appendNumberOfDigitsInbox(row, entitySequence); appendLastValueInbox(row, entitySequence); appendOperations(row, entitySequence); if ( entitySequence.isAlreadyInUse() ) { row.setTooltiptext(_("Code sequence is already in use and cannot be updated")); } if ( (row.getPreviousSibling() != null) && !((EntitySequence) ((Row) row.getPreviousSibling()).getValue()).getEntityName() .equals(entityName)) { row.setClass("separator"); } } private void appendActiveRadiobox(final Row row, final EntitySequence entitySequence) { final Radio radiobox = Util.bind( new Radio(), entitySequence::isActive, value -> { updateOtherSequences(entitySequence); entitySequence.setActive(value); Util.reloadBindings(entitySequencesGrid); reloadEntitySequences(); }); row.appendChild(radiobox); } private void appendPrefixTextbox(Row row, final EntitySequence entitySequence) { final Textbox tempTextbox = new Textbox(); tempTextbox.setWidth("200px"); Textbox textbox = Util.bind( tempTextbox, entitySequence::getPrefix, value -> { try { entitySequence.setPrefix(value); } catch (IllegalArgumentException e) { throw new WrongValueException(tempTextbox, e.getMessage()); } }); textbox.setConstraint(checkConstraintFormatPrefix()); if ( entitySequence.isAlreadyInUse() ) { textbox.setDisabled(true); } row.appendChild(textbox); } private void appendNumberOfDigitsInbox(Row row, final EntitySequence entitySequence) { final Intbox tempIntbox = new Intbox(); Intbox intbox = Util.bind( tempIntbox, entitySequence::getNumberOfDigits, value -> { try { entitySequence.setNumberOfDigits(value); } catch (IllegalArgumentException e) { throw new WrongValueException( tempIntbox, _("number of digits must be between {0} and {1}", EntitySequence.MIN_NUMBER_OF_DIGITS, EntitySequence.MAX_NUMBER_OF_DIGITS)); } }); intbox.setConstraint(checkConstraintNumberOfDigits()); if ( entitySequence.isAlreadyInUse() ) { intbox.setDisabled(true); } row.appendChild(intbox); } private void appendLastValueInbox(Row row, final EntitySequence entitySequence) { Textbox textbox = Util.bind( new Textbox(), () -> EntitySequence.formatValue( entitySequence.getNumberOfDigits(), entitySequence.getLastValue())); row.appendChild(textbox); } private void appendOperations(final Row row, final EntitySequence entitySequence) { final Button removeButton = Util.createRemoveButton(event -> { if ( isLastOne(entitySequence) ) { showMessageNotDelete(); } else { removeEntitySequence(entitySequence); } }); if ( entitySequence.isAlreadyInUse() ) { removeButton.setDisabled(true); } row.appendChild(removeButton); } } private void updateOtherSequences(final EntitySequence activeSequence) { for (EntitySequence sequence : getEntitySequences(activeSequence.getEntityName())) { sequence.setActive(false); } } private Constraint checkConstraintFormatPrefix() { return (comp, value) -> { Row row = (Row) comp.getParent(); EntitySequence sequence = row.getValue(); if ( !sequence.isAlreadyInUse() ) { String errorMessage = validPrefix(sequence, (String) value); if ( errorMessage != null ) { throw new WrongValueException(comp, errorMessage); } } }; } private String validPrefix(EntitySequence sequence, String prefixValue) { sequence.setPrefix(prefixValue); if ( !configurationModel.checkPrefixFormat(sequence) ) { String message = _("Invalid format prefix. Format prefix cannot be empty, contain '_' or contain whitespaces."); if ( sequence.getEntityName().canContainLowBar() ) { message = _("format prefix invalid. It cannot be empty or contain whitespaces."); } return message; } return null; } private Constraint checkConstraintNumberOfDigits() { return (comp, value) -> { Row row = (Row) comp.getParent(); EntitySequence sequence = row.getValue(); if ( !sequence.isAlreadyInUse() ) { Integer numberOfDigits = (Integer) value; try { sequence.setNumberOfDigits(numberOfDigits); } catch (IllegalArgumentException e) { throw new WrongValueException( comp, _("number of digits must be between {0} and {1}", EntitySequence.MIN_NUMBER_OF_DIGITS, EntitySequence.MAX_NUMBER_OF_DIGITS)); } } }; } private void addEntitySequence(EntityNameEnum entityName, String prefix, Integer digits) { configurationModel.addEntitySequence(entityName, prefix, digits); reloadEntitySequences(); } private List<EntitySequence> getEntitySequences(EntityNameEnum entityName) { return configurationModel.getEntitySequences(entityName); } private boolean isLastOne(EntitySequence sequence) { return getEntitySequences(sequence.getEntityName()).size() == 1; } private void showMessageNotDelete() { Messagebox.show( _("It can not be deleted. At least one sequence is necessary."), _("Deleting sequence"), Messagebox.OK, Messagebox.INFORMATION); } /** * Used in configuration.zul * Should be public! */ public EntitySequenceGroupRenderer getEntitySequenceGroupRenderer() { return new EntitySequenceGroupRenderer(); } private List<EntitySequence> getAllEntitySequences() { List<EntitySequence> allSequences = new ArrayList<>(); for (final EntityNameEnum entityName : EntityNameEnum.values()) { allSequences.addAll(this.getEntitySequences(entityName)); } return allSequences; } /** * Used in configuration.zul * Should be public! */ public void addNewEntitySequence() { if ( entityCombo != null && numDigitBox != null ) { if ( entityCombo.getSelectedItem() == null ) { throw new WrongValueException(entityCombo, _("Select entity, please")); } if ( prefixBox.getValue() == null || prefixBox.getValue().isEmpty() ) { throw new WrongValueException(prefixBox, _("cannot be empty")); } try { addEntitySequence( entityCombo.getSelectedItem().getValue(), prefixBox.getValue(), numDigitBox.getValue()); } catch (IllegalArgumentException e) { throw new WrongValueException(numDigitBox, e.getMessage()); } } } /** * Used in configuration.zul * Should be public! */ public EntityNameEnum[] getEntityNames() { return EntityNameEnum.values(); } /** * Tab LDAP properties. */ public LDAPConfiguration getLdapConfiguration() { return configurationModel.getLdapConfiguration(); } /** * Used in configuration.zul * Should be public! */ public void setLdapConfiguration(LDAPConfiguration ldapConfiguration) { configurationModel.setLdapConfiguration(ldapConfiguration); } /** * Used in configuration.zul * Should be public! */ public RowRenderer getAllUserRolesRenderer() { return (row, o, i) -> { final UserRole role = (UserRole) o; row.appendChild(new Label(role.getDisplayName())); final Textbox tempTextbox = new Textbox(); Textbox textbox = Util.bind( tempTextbox, () -> { List<String> listRoles = configurationModel.getLdapConfiguration().getMapMatchingRoles().get(role.name()); Collections.sort(listRoles); return StringUtils.join(listRoles, ";"); }, value -> { // Created a set in order to avoid duplicates Set<String> rolesLdap = new HashSet<>(Arrays.asList(StringUtils.split(value, ";"))); configurationModel.getLdapConfiguration().setConfigurationRolesLdap(role.name(), rolesLdap); }); textbox.setWidth("300px"); row.appendChild(textbox); }; } public UserRole[] getRoles() { return UserRole.values(); } /** * Used in configuration.zul * Should be public! */ public boolean isChangedDefaultPasswdAdmin() { return configurationModel.isChangedDefaultPasswdAdmin(); } /** * Used in configuration.zul * Should be public! */ public boolean isLdapPropertyStrategy() { return !getLdapConfiguration().getLdapGroupStrategy(); } /** * Used in configuration.zul * Should be public! */ public boolean isCheckNewVersionEnabled() { return configurationModel.isCheckNewVersionEnabled(); } /** * Used in configuration.zul * Should be public! */ public void setCheckNewVersionEnabled(boolean checkNewVersionEnabled) { configurationModel.setCheckNewVersionEnabled(checkNewVersionEnabled); } /** * Used in configuration.zul * Should be public! */ public Set<String> getCurrencies() { return configurationModel.getCurrencies(); } /** * Used in configuration.zul * Should be public! */ public ListitemRenderer getCurrencyRenderer() { return (listitem, o, i) -> { String currencyCode = (String) o; listitem.setLabel(currencyCode + " - " + configurationModel.getCurrencySymbol(currencyCode)); listitem.setValue(currencyCode); }; } /** * Used in configuration.zul * Should be public! */ public String getSelectedCurrency() { return configurationModel.getCurrencyCode(); } /** * Used in configuration.zul * Should be public! */ public void setSelectedCurrency(String currencyCode) { configurationModel.setCurrency(currencyCode); } /** * Used in configuration.zul * Should be public! */ public TypeOfWorkHours getPersonalTimesheetsTypeOfWorkHours() { return configurationModel.getPersonalTimesheetsTypeOfWorkHours(); } /** * Used in configuration.zul * Should be public! */ public void setPersonalTimesheetsTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { configurationModel.setPersonalTimesheetsTypeOfWorkHours(typeOfWorkHours); } /** * Used in configuration.zul * Should be public! */ public TypeOfWorkHours getBudgetDefaultTypeOfWorkHours() { return configurationModel.getBudgetDefaultTypeOfWorkHours(); } /** * Used in configuration.zul * Should be public! */ public void setBudgetDefaultTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { configurationModel.setBudgetDefaultTypeOfWorkHours(typeOfWorkHours); } /** * Used in configuration.zul * Should be public! */ public Boolean getEnabledAutomaticBudget() { return configurationModel.getEnabledAutomaticBudget(); } /** * Used in configuration.zul * Should be public! */ public void setEnabledAutomaticBudget(Boolean enabledAutomaticBudget) { configurationModel.setEnabledAutomaticBudget(enabledAutomaticBudget); } /** * Used in configuration.zul * Should be public! */ public List<PersonalTimesheetsPeriodicityEnum> getPersonalTimesheetsPeriodicities() { return Arrays.asList(PersonalTimesheetsPeriodicityEnum.values()); } /** * Used in configuration.zul * Should be public! */ public ListitemRenderer getPersonalTimesheetsPeriodicityRenderer() { return (listitem, o, i) -> { PersonalTimesheetsPeriodicityEnum periodicity = (PersonalTimesheetsPeriodicityEnum) o; listitem.setLabel(_(periodicity.getName())); listitem.setValue(periodicity); }; } /** * Used in configuration.zul * Should be public! */ public PersonalTimesheetsPeriodicityEnum getSelectedPersonalTimesheetsPeriodicity() { return configurationModel.getPersonalTimesheetsPeriodicity(); } /** * Used in configuration.zul * Should be public! */ public void setSelectedPersonalTimesheetsPeriodicity( PersonalTimesheetsPeriodicityEnum personalTimesheetsPeriodicity) { configurationModel.setPersonalTimesheetsPeriodicity(personalTimesheetsPeriodicity); } private boolean isPersonalTimesheetsPeriodicityDisabled() { return configurationModel.isAnyPersonalTimesheetAlreadySaved(); } /** * Used in configuration.zul * Should be public! */ public String getPersonalTimesheetsPeriodicityTooltip() { return isPersonalTimesheetsPeriodicityDisabled() ? _("Periocity cannot be changed because there is already any personal timesheet stored") : ""; } /** * Used in configuration.zul * Should be public! */ public Integer getSecondsPlanningWarning() { return configurationModel.getSecondsPlanningWarning(); } /** * Used in configuration.zul * Should be public! */ public void setSecondsPlanningWarning(Integer secondsPlanningWarning) { configurationModel.setSecondsPlanningWarning(secondsPlanningWarning); } /** * Used in configuration.zul * Should be public! */ public String getRepositoryLocation() { return configurationModel.getRepositoryLocation(); } /** * Used in configuration.zul * Should be public! */ public void setRepositoryLocation(String location) { configurationModel.setRepositoryLocation(location); } public List<Connector> getConnectors() { return configurationModel.getConnectors(); } public Connector getSelectedConnector() { return selectedConnector; } /** * Used in configuration.zul * Should be public! */ public void setSelectedConnector(Connector connector) { selectedConnector = connector; Util.reloadBindings(connectorPropertriesGrid); } /** * Used in configuration.zul * Should be public! */ public List<ConnectorProperty> getConnectorPropertries() { return selectedConnector == null ? Collections.emptyList() : selectedConnector.getProperties(); } /** * Used in configuration.zul * Should be public! */ public RowRenderer getConnectorPropertriesRenderer() { return new RowRenderer() { @Override public void render(Row row, Object o, int i) throws Exception { ConnectorProperty property = (ConnectorProperty) o; row.setValue(property); Util.appendLabel(row, _(property.getKey())); if ("Protocol".equals(property.getKey())) { appendValueCombobox(row, property); } else { 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, property::getValue, property::setValue); if ( property.getKey().equals(PredefinedConnectorProperties.PASSWORD) || property.getKey().equals(PredefinedConnectorProperties.EMAIL_PASSWORD) ) { textbox.setType("password"); } // Need for method validateEmailFields() if ( property.getKey().equals(PredefinedConnectorProperties.EMAIL_USERNAME) ) { emailUsernameTextbox = textbox; } if ( property.getKey().equals(PredefinedConnectorProperties.EMAIL_PASSWORD) ) { emailPasswordTextbox = textbox; } if ( property.getKey().equals(PredefinedConnectorProperties.EMAIL_SENDER) ) { emailSenderTextbox = textbox; } row.appendChild(textbox); } private void appendValueCombobox(Row row, final ConnectorProperty property){ final Combobox combobox = new Combobox(); combobox.setWidth("400px"); final List<String> protocols = new ArrayList<>(); protocols.add("SMTP"); protocols.add(STARTTLS_PROTOCOL); for (String item : protocols){ Comboitem comboitem = new Comboitem(); comboitem.setValue(item); comboitem.setLabel(item); comboitem.setParent(combobox); if ( (!"".equals(property.getValue())) && (item.equals(property.getValue())) ) { combobox.setSelectedItem(comboitem); } } combobox.addEventListener( Events.ON_SELECT, event -> { if ( combobox.getSelectedItem() != null ){ property.setValue(combobox.getSelectedItem().getValue().toString()); } }); Util.bind( combobox, combobox::getSelectedItem, item -> { if ( (item != null) && (item.getValue() != null) && (item.getValue() instanceof String) ) { property.setValue(combobox.getSelectedItem().getValue().toString()); } }); row.appendChild(combobox); // Needed for testing E-mail connection protocolsCombobox = combobox; } Constraint checkPropertyValue(final ConnectorProperty property) { final String key = property.getKey(); return (comp, value) -> { if ( key.equals(PredefinedConnectorProperties.ACTIVATED) ) { if ( !"Y".equalsIgnoreCase((String) value) && !"N".equalsIgnoreCase((String) value)) { 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) || key.equals(PredefinedConnectorProperties.HOST) || key.equals(PredefinedConnectorProperties.PORT) || key.equals(PredefinedConnectorProperties.EMAIL_SENDER) || key.equals(PredefinedConnectorProperties.PROTOCOL) ) { ((InputElement) comp).setConstraint("no empty:" + _("cannot be empty")); } else if ( key.equals(PredefinedConnectorProperties.TIM_NR_DAYS_TIMESHEET) || key.equals(PredefinedConnectorProperties.TIM_NR_DAYS_ROSTER) || key.equals(PredefinedConnectorProperties.PORT) ) { 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; } } }; } private boolean areEmailFieldsValid() { if ( protocolsCombobox != null && protocolsCombobox.getSelectedItem() != null ) { boolean isNotNullValue = emailUsernameTextbox.getValue() != null && emailPasswordTextbox.getValue() != null && emailUsernameTextbox.getValue().length() != 0 && emailPasswordTextbox.getValue().length() != 0; if ( STARTTLS_PROTOCOL.equals(protocolsCombobox.getSelectedItem().getLabel()) && isNotNullValue && emailSenderTextbox.getValue().matches("^\\S+@\\S+\\.\\S+$") ) { return true; } if ( protocolsCombobox.getSelectedItem() != null && "SMTP".equals(protocolsCombobox.getSelectedItem().getLabel()) ) { return true; } } return false; } /** * Upload image to classes folder via ZK Fileupload. * * Used in configuration.zul * Should be public! * * @param media */ public void importLogo(Media media) { if ( Util.logo != null ) { /* We are going to overwrite existing logo */ removeLogo(); } if ( Util.logo == null ) { if ( checkFormat(media.getFormat()) ) { BufferedInputStream in; BufferedOutputStream out = null; File fileToSave; InputStream inputStream = media.getStreamData(); in = new BufferedInputStream(inputStream); try { fileToSave = new File( ContextLoaderListener .getCurrentWebApplicationContext() .getResource("/") .getFile() .getPath() + "\\" + media.getName()); OutputStream outputStream = new FileOutputStream(fileToSave); out = new BufferedOutputStream(outputStream); byte[] buffer = new byte[1024]; int ch = in.read(buffer); while ( ch != -1 ) { out.write(buffer, 0, ch); ch = in.read(buffer); } } catch (IOException ignored) { } finally { try { if (out != null) out.close(); in.close(); } catch (IOException ignored) { } } Util.setLogoFromTarget(media.getName()); configurationModel.setCompanyLogoURL(media.getName()); ((Textbox) configurationWindow.getFellow("companyLogoURL")).setValue(media.getName()); ((org.zkoss.zul.Image) configurationWindow.getFellow(LOGO_PREVIEW_COMPONENT)).setContent(Util.logo); } else { messages.showMessage(Level.WARNING, _("The only current supported formats are png and jpeg")); } } } private boolean checkFormat(String format) { /* http://stackoverflow.com/questions/23424399/jpg-vs-jpeg-image-formats */ return format.matches("(?i).*png") || format.matches("(?i).*jpeg") || format.matches("(?i).*jpg"); } /** * Handler of remove logo button. * * Should be public! */ public void removeLogo() { if ( !"".equals(companyLogoURL.getValue()) ) { ((org.zkoss.zul.Image) configurationWindow.getFellow(LOGO_PREVIEW_COMPONENT)).setSrc(""); findAndRemoveLogoFromTarget(companyLogoURL.getValue()); Util.logo = null; } companyLogoURL.setValue(""); configurationModel.setCompanyLogoURL(""); } /** * Setting preview image. * * Used in configuration.zul * Should be public! */ public void setPreviewLogo() { if ( !"".equals(companyLogoURL.getValue()) ) { ((org.zkoss.zul.Image) configurationWindow.getFellow(LOGO_PREVIEW_COMPONENT)).setContent(Util.logo); } } /** * Trying to delete file from classes folder. */ private void findAndRemoveLogoFromTarget(String name) { File fileToDelete; try { fileToDelete = ContextLoaderListener.getCurrentWebApplicationContext().getResource(name).getFile(); fileToDelete.delete(); } catch (IOException ignored) { } } }