/**
* DataCleaner (community edition)
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.datacleaner.windows;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.datacleaner.Version;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.configuration.DataCleanerConfiguration;
import org.datacleaner.configuration.RemoteServerData;
import org.datacleaner.descriptors.RemoteDescriptorProvider;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.restclient.RESTClient;
import org.datacleaner.restclient.RESTClientImpl;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.ImageManager;
import org.datacleaner.util.RemoteServersUtils;
import org.datacleaner.util.WidgetFactory;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.widgets.DCHtmlBox;
import org.jdesktop.swingx.JXEditorPane;
import org.jdesktop.swingx.JXTextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataCloudLogInWindow extends AbstractDialog {
class ClearErrorLabelDocumentListener implements DocumentListener {
@Override
public void insertUpdate(final DocumentEvent e) {
showError("");
}
@Override
public void removeUpdate(final DocumentEvent e) {
showError("");
}
@Override
public void changedUpdate(final DocumentEvent e) {
showError("");
}
}
public static final String SHOW_DATACLOUD_DIALOG_USER_PREFERENCE = "show.datacloud.dialog";
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(DataCloudLogInWindow.class);
private static final int PADDING = WidgetUtils.BORDER_WIDE_WIDTH;
private static final String LOGIN_CARD = "Card with login form";
private static final String SUCCESS_CARD = "Card with information about successful login";
private static final String TERMS_CARD = "Card with terms and conditions";
private final DataCleanerConfiguration _configuration;
private final UserPreferences _userPreferences;
private final JComponent _contentPanel;
Image bannerImage =
ImageManager.get().getImage("images/datacloud_banner.png").getScaledInstance(530, 303, Image.SCALE_SMOOTH);
private JPanel cards;
private JEditorPane invalidCredentialsLabel;
private JXTextField usernameTextField;
private JPasswordField passwordTextField;
private JCheckBox dontShowAgainCheckBox;
private DCPanel loginCard;
private DCPanel successCard;
private DCPanel termsCard;
private JXEditorPane tacArea = new JXEditorPane();
private JButton acceptButton;
@Inject
public DataCloudLogInWindow(final DataCleanerConfiguration configuration, final UserPreferences userPreferences,
final WindowContext windowContext, final AbstractWindow owner) {
super(windowContext, null, owner);
_configuration = configuration;
_userPreferences = userPreferences;
_contentPanel = createContentPanel();
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "EnterAction");
getRootPane().getActionMap().put("EnterAction", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
if (loginCard.isVisible()) {
signIn();
} else if (termsCard.isVisible()) {
acceptTerms();
} else if (successCard.isVisible()) {
close();
}
}
});
}
public static boolean isRelevantToShow(final UserPreferences userPreferences,
final DataCleanerConfiguration configuration, final boolean afterStart) {
final RemoteServerData datacloudConfig = configuration.getEnvironment().getRemoteServerConfiguration()
.getServerConfig(RemoteDescriptorProvider.DATACLOUD_SERVER_NAME);
final String showDataCloudDialog =
userPreferences.getAdditionalProperties().getOrDefault(SHOW_DATACLOUD_DIALOG_USER_PREFERENCE, "true");
final Boolean showDataCloudDialogBool = Boolean.parseBoolean(showDataCloudDialog);
final boolean noAccount = datacloudConfig == null;
if (afterStart) {
return noAccount && showDataCloudDialogBool;
} else {
return noAccount;
}
}
private JComponent createContentPanel() {
final DCPanel result = new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND);
final GroupLayout layout = new GroupLayout(result);
result.setLayout(layout);
result.setBorder(new EmptyBorder(15, 15, 15, 15));
invalidCredentialsLabel = new DCHtmlBox("");
invalidCredentialsLabel.setBorder(WidgetUtils.BORDER_EMPTY);
invalidCredentialsLabel.setOpaque(false);
final JEditorPane resetPasswordText = new DCHtmlBox(
"Forgot your password? " + "<a href='https://datacleaner.org/reset_password'>Reset it here</a>.");
final CardLayout cardLayout = new CardLayout();
cards = new JPanel(cardLayout);
cards.setOpaque(true);
final DCPanel bottomPanel = contentBottom();
loginCard = contentLoginCard();
successCard = contentSuccessCard();
termsCard = contentTermsAndConditions();
cards.add(loginCard, LOGIN_CARD);
cards.add(successCard, SUCCESS_CARD);
cards.add(termsCard, TERMS_CARD);
cardLayout.show(cards, LOGIN_CARD);
layout.setHorizontalGroup(layout.createParallelGroup().addComponent(cards).addComponent(bottomPanel)
.addComponent(invalidCredentialsLabel).addComponent(resetPasswordText));
layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(cards, 300, cards.getPreferredSize().height + 36, cards.getPreferredSize().height + 36)
.addGap(PADDING).addComponent(invalidCredentialsLabel).addComponent(resetPasswordText).addGap(PADDING)
.addComponent(bottomPanel, bottomPanel.getPreferredSize().height, bottomPanel.getPreferredSize().height,
bottomPanel.getPreferredSize().height));
return result;
}
private DCPanel contentLoginCard() {
// 1. Create components
final JEditorPane informationText = createDataCloudInformationText();
informationText.setBorder(WidgetUtils.BORDER_EMPTY);
// Set initially two lines of empty text for preferred size enough for 2-lines error message.
final JLabel usernameLabel = new JLabel();
final JLabel passwordLabel = new JLabel();
usernameTextField = WidgetFactory.createTextField("email address");
usernameTextField.setName("email address");
passwordTextField = WidgetFactory.createPasswordField();
passwordTextField.setName("password");
final JButton signInButton = WidgetFactory.createPrimaryButton("Sign in", IconUtils.ACTION_SAVE_BRIGHT);
final JLabel banner = new JLabel(new ImageIcon(bannerImage));
final ImageIcon usernameIcon = ImageManager.get().getImageIcon(IconUtils.USERNAME_INPUT);
final ImageIcon passwordIcon = ImageManager.get().getImageIcon(IconUtils.PASSWORD_INPUT);
usernameLabel.setIcon(usernameIcon);
usernameLabel.setVerticalTextPosition(JLabel.CENTER);
usernameLabel.setHorizontalAlignment(JLabel.CENTER);
usernameLabel.setBackground(new Color(225, 225, 225));
usernameLabel.setOpaque(true);
passwordLabel.setIcon(passwordIcon);
passwordLabel.setVerticalTextPosition(JLabel.CENTER);
passwordLabel.setHorizontalAlignment(JLabel.CENTER);
passwordLabel.setBackground(new Color(225, 225, 225));
passwordLabel.setOpaque(true);
// 2. Layouts
final DCPanel loginCard = new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND);
loginCard.setOpaque(true);
final GroupLayout loginLayout = new GroupLayout(loginCard);
loginCard.setLayout(loginLayout);
final int textFieldHeight = signInButton.getPreferredSize().height;
loginLayout.setVerticalGroup(loginLayout.createSequentialGroup()
.addComponent(banner, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE,
GroupLayout.PREFERRED_SIZE).addGap(PADDING * 2)
.addComponent(informationText, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE,
GroupLayout.PREFERRED_SIZE).addGap(PADDING * 2).addGroup(
loginLayout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(usernameTextField, GroupLayout.PREFERRED_SIZE, textFieldHeight,
textFieldHeight)
.addComponent(usernameLabel, GroupLayout.DEFAULT_SIZE, textFieldHeight, textFieldHeight)
.addComponent(passwordTextField, GroupLayout.PREFERRED_SIZE, textFieldHeight,
textFieldHeight)
.addComponent(passwordLabel, GroupLayout.DEFAULT_SIZE, textFieldHeight, textFieldHeight)
.addComponent(signInButton, GroupLayout.DEFAULT_SIZE, textFieldHeight, textFieldHeight))
.addGap(PADDING));
loginLayout.setHorizontalGroup(
loginLayout.createParallelGroup().addComponent(banner).addComponent(informationText).addGroup(
loginLayout.createSequentialGroup().addGap(PADDING, PADDING, Integer.MAX_VALUE)
.addComponent(usernameLabel, textFieldHeight, textFieldHeight, textFieldHeight)
.addComponent(usernameTextField, GroupLayout.DEFAULT_SIZE, 150, 150).addGap(PADDING)
.addComponent(passwordLabel, textFieldHeight, textFieldHeight, textFieldHeight)
.addComponent(passwordTextField, GroupLayout.DEFAULT_SIZE, 150, 150).addGap(PADDING)
.addComponent(signInButton).addGap(PADDING, PADDING, Integer.MAX_VALUE)));
// 3. Add listeners
signInButton.addActionListener(e -> signIn());
final ClearErrorLabelDocumentListener clearErrorListener = new ClearErrorLabelDocumentListener();
usernameTextField.getDocument().addDocumentListener(clearErrorListener);
passwordTextField.getDocument().addDocumentListener(clearErrorListener);
return loginCard;
}
private DCPanel contentTermsAndConditions() {
final DCPanel p = new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND);
final GroupLayout l = new GroupLayout(p);
final JLabel description = new JLabel("Please review terms and conditions before using DataCloud services");
acceptButton = WidgetFactory.createPrimaryButton("Accept", IconUtils.ACTION_SAVE_BRIGHT);
tacArea = new JXEditorPane();
final JScrollPane tacScroll = new JScrollPane(tacArea);
tacScroll.setBorder(BorderFactory.createLineBorder(Color.GRAY));
tacScroll.setPreferredSize(new Dimension(100, 200));
tacScroll.getHorizontalScrollBar().setEnabled(false);
tacScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
p.setLayout(l);
p.setOpaque(true);
l.setVerticalGroup(l.createSequentialGroup().addGap(PADDING).addComponent(description).addGap(PADDING)
.addComponent(tacScroll).addGap(PADDING * 2).addComponent(acceptButton).addGap(PADDING));
l.setHorizontalGroup(l.createParallelGroup().addComponent(description, GroupLayout.Alignment.LEADING)
.addComponent(tacScroll, GroupLayout.Alignment.CENTER)
.addComponent(acceptButton, GroupLayout.Alignment.CENTER));
acceptButton.addActionListener(e -> acceptTerms());
return p;
}
private DCPanel contentSuccessCard() {
final DCPanel result = new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND);
final BorderLayout borderLayout = new BorderLayout();
result.setLayout(borderLayout);
final JLabel banner = new JLabel(new ImageIcon(bannerImage));
// Green color BG_COLOR_GREEN_MEDIUM = #7ABE44
final JLabel successLoginText = new JLabel(
"<html><center>You have been <font color='#7ABE44'>successfully</font> logged on."
+ "<br><br>Thank you!</center></html>");
successLoginText.setHorizontalAlignment(JLabel.CENTER);
successLoginText.setFont(WidgetUtils.FONT_BANNER);
result.add(banner, BorderLayout.NORTH);
result.add(successLoginText, BorderLayout.CENTER);
return result;
}
private DCPanel contentBottom() {
final String showDataCloudDialog =
_userPreferences.getAdditionalProperties().getOrDefault(SHOW_DATACLOUD_DIALOG_USER_PREFERENCE, "false");
final Boolean showDataCloudDialogBool = Boolean.parseBoolean(showDataCloudDialog);
final JButton closeButton = WidgetFactory.createDefaultButton("Close", IconUtils.ACTION_CLOSE_DARK);
dontShowAgainCheckBox = new JCheckBox("Don't show again.", !showDataCloudDialogBool);
dontShowAgainCheckBox.setOpaque(false);
final DCPanel result = new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND);
final BorderLayout borderLayout = new BorderLayout();
result.setLayout(borderLayout);
result.add(dontShowAgainCheckBox, BorderLayout.WEST);
result.add(closeButton, BorderLayout.EAST);
closeButton.addActionListener(e -> {
saveDontShowFlag();
close();
});
return result;
}
@Override
protected String getBannerTitle() {
return getWindowTitle();
}
@Override
protected int getDialogWidth() {
return 560;
}
@Override
protected JComponent getDialogContent() {
return _contentPanel;
}
protected boolean isWindowResizable() {
return true;
}
@Override
public String getWindowTitle() {
return "Sign in to DataCloud";
}
@Override
public Image getWindowIcon() {
return ImageManager.get().getImage(IconUtils.MENU_DATACLOUD);
}
@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
private JEditorPane createDataCloudInformationText() {
final DCHtmlBox editorPane = new DCHtmlBox("");
editorPane.setSize(getDialogWidth() - 30, Integer.MAX_VALUE);
editorPane.setText("<html>Thank you for using DataCleaner."
+ " If you're a registered user on <a href=\"https://datacleaner.org\">datacleaner.org</a> then you can immediately access"
+ " our cloud data services (there are free credits with your registration). DataCloud contains services such as:"
+ "<ul style=\"list-style-type:none\">"
+ " <li>\u2022 Address correction using postal data from all over the world."
+ " <li>\u2022 Check the validity and standardize formatting of Email addresses and Phone numbers."
+ " <li>\u2022 Enrichment of contact information using mover's registries, deceased lists and more."
+ "</ul>");
editorPane.setOpaque(false);
//editorPane.setFont(WidgetUtils.FONT_HEADER2);
return editorPane;
}
private void signIn() {
invalidCredentialsLabel.setText("Verifying credentials...");
invalidCredentialsLabel.setForeground(null);
SwingUtilities.invokeLater(() -> {
final String userName = usernameTextField.getText();
try {
checkServer();
} catch (final Exception ex) {
final String msg = ex.getMessage();
if (msg != null && msg.toLowerCase().contains("terms and conditions")) {
showError("");
try {
final String tac = RemoteServersUtils.getDataCloudTermsAndConditions();
tacArea.setContentType("text/html");
tacArea.setText(tac);
tacArea.setCaretPosition(0);
} catch (final IOException e) {
showError("Cannot download DataCloud Terms and Conditions from the website: " + e.toString()
+ ".<br/>Please, visit <a href=\"" + RemoteDescriptorProvider.DATACLOUD_TERMS_URL
+ "\">" + RemoteDescriptorProvider.DATACLOUD_TERMS_URL + "</a>");
tacArea.setText(
"Cannot download DataCloud Terms and Conditions from the website: " + e.toString()
+ "\n\nPlease, visit " + RemoteDescriptorProvider.DATACLOUD_TERMS_URL);
acceptButton.setEnabled(false);
}
final CardLayout cardLayout = (CardLayout) cards.getLayout();
cardLayout.show(cards, TERMS_CARD);
return;
} else {
showError("Sign in to DataCloud failed: " + ex.getMessage());
logger.warn("Sign in to DataCloud failed for user '{}'", userName, ex);
return;
}
}
showError("");
logger.debug("Sign in to DataCloud succeeded. User name: {}", userName);
addRemoteServer();
});
}
private void showError(String str) {
if (str == null || str.isEmpty()) {
str = "";
}
invalidCredentialsLabel.setForeground(WidgetUtils.ADDITIONAL_COLOR_RED_BRIGHT);
invalidCredentialsLabel.setText(str);
invalidCredentialsLabel.invalidate();
_contentPanel.invalidate();
_contentPanel.validate();
_contentPanel.revalidate();
_contentPanel.repaint();
}
private void acceptTerms() {
try {
// call web service to accept T&C
final RESTClientImpl client =
new RESTClientImpl(usernameTextField.getText(), new String(passwordTextField.getPassword()),
Version.getVersion());
client.getResponse(RESTClient.HttpMethod.POST, RemoteDescriptorProvider.DATACLOUD_TERMS_ACCEPT_URL, "");
addRemoteServer();
dontShowAgainCheckBox.setVisible(false);
} catch (final Exception e) {
showError("Cannot send acceptance message to server: " + e.getMessage());
}
}
protected void addRemoteServer() {
RemoteServersUtils
.addRemoteServer(_configuration.getEnvironment(), RemoteDescriptorProvider.DATACLOUD_SERVER_NAME,
RemoteDescriptorProvider.DATACLOUD_URL, usernameTextField.getText(),
new String(passwordTextField.getPassword()));
final CardLayout cardLayout = (CardLayout) cards.getLayout();
cardLayout.show(cards, SUCCESS_CARD);
}
protected void checkServer() throws Exception {
final String userName = usernameTextField.getText();
final String pass = new String(passwordTextField.getPassword());
RemoteServersUtils.checkServerWithCredentials(RemoteDescriptorProvider.DATACLOUD_URL, userName, pass);
}
private void saveDontShowFlag() {
final Boolean selectedNeg = !dontShowAgainCheckBox.isSelected();
_userPreferences.getAdditionalProperties().put(SHOW_DATACLOUD_DIALOG_USER_PREFERENCE, selectedNeg.toString());
_userPreferences.save();
}
public void open() {
super.open();
usernameTextField.requestFocusInWindow();
}
public static void main(final String[] args) {
final AtomicInteger i = new AtomicInteger();
final DataCloudLogInWindow w = new DataCloudLogInWindow(null, null, null, null) {
protected void checkServer() {
System.out.println("Check server");
if (i.getAndIncrement() < 1) {
try {
Thread.sleep(500);
} catch (final InterruptedException e) {
e.printStackTrace();
}
throw new RuntimeException("TEST ERROR WITH SOME LONGER (ALTHOUGH NOT SO LONG) TEXT. TRY AGAIN.");
}
throw new RuntimeException("DataCloud terms and conditions not accepted");
}
protected void addRemoteServer() {
System.out.println("Add server");
}
};
w.open();
}
}