001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.oauth;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.FlowLayout;
008import java.awt.GridBagConstraints;
009import java.awt.GridBagLayout;
010import java.awt.Insets;
011import java.awt.event.ActionEvent;
012import java.beans.PropertyChangeEvent;
013import java.beans.PropertyChangeListener;
014import java.util.concurrent.Executor;
015
016import javax.swing.AbstractAction;
017import javax.swing.BorderFactory;
018import javax.swing.JButton;
019import javax.swing.JCheckBox;
020import javax.swing.JLabel;
021import javax.swing.JPanel;
022import javax.swing.JTabbedPane;
023import javax.swing.event.DocumentEvent;
024import javax.swing.event.DocumentListener;
025import javax.swing.text.JTextComponent;
026
027import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
028import org.openstreetmap.josm.data.oauth.OAuthToken;
029import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator;
030import org.openstreetmap.josm.gui.widgets.HtmlPanel;
031import org.openstreetmap.josm.gui.widgets.JosmTextField;
032import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
033import org.openstreetmap.josm.tools.ImageProvider;
034
035/**
036 * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully manual process.
037 *
038 * @since 2746
039 */
040public class ManualAuthorizationUI extends AbstractAuthorizationUI {
041
042    private final JosmTextField tfAccessTokenKey = new JosmTextField();
043    private transient AccessTokenKeyValidator valAccessTokenKey;
044    private final JosmTextField tfAccessTokenSecret = new JosmTextField();
045    private transient AccessTokenSecretValidator valAccessTokenSecret;
046    private final JCheckBox cbSaveToPreferences = new JCheckBox(tr("Save Access Token in preferences"));
047    private final HtmlPanel pnlMessage = new HtmlPanel();
048    private final transient Executor executor;
049
050    /**
051     * Constructs a new {@code ManualAuthorizationUI} for the given API URL.
052     * @param apiUrl The OSM API URL
053     * @param executor the executor used for running the HTTP requests for the authorization
054     * @since 5422
055     */
056    public ManualAuthorizationUI(String apiUrl, Executor executor) {
057        super(/* dont pass apiURL because setApiUrl is overridden and references a local field */);
058        setApiUrl(apiUrl);
059        this.executor = executor;
060        build();
061    }
062
063    protected JPanel buildAccessTokenPanel() {
064        JPanel pnl = new JPanel(new GridBagLayout());
065        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
066        GridBagConstraints gc = new GridBagConstraints();
067        AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder();
068
069        // the access token key input field
070        gc.anchor = GridBagConstraints.NORTHWEST;
071        gc.fill = GridBagConstraints.HORIZONTAL;
072        gc.weightx = 0.0;
073        gc.gridwidth = 2;
074        gc.insets = new Insets(0, 0, 5, 0);
075        pnlMessage.setText("<html><body>"
076                + tr("Please enter an OAuth Access Token which is authorized to access the OSM server "
077                        + "''{0}''.",
078                        getApiUrl()) + "</body></html>");
079        pnl.add(pnlMessage, gc);
080
081        // the access token key input field
082        gc.gridy = 1;
083        gc.weightx = 0.0;
084        gc.gridwidth = 1;
085        gc.insets = new Insets(0, 0, 0, 3);
086        pnl.add(new JLabel(tr("Access Token Key:")), gc);
087
088        gc.gridx = 1;
089        gc.weightx = 1.0;
090        pnl.add(tfAccessTokenKey, gc);
091        SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenKey);
092        valAccessTokenKey = new AccessTokenKeyValidator(tfAccessTokenKey);
093        valAccessTokenKey.validate();
094        tfAccessTokenKey.getDocument().addDocumentListener(accessTokenBuilder);
095
096        // the access token key input field
097        gc.gridy = 2;
098        gc.gridx = 0;
099        gc.weightx = 0.0;
100        pnl.add(new JLabel(tr("Access Token Secret:")), gc);
101
102        gc.gridx = 1;
103        gc.weightx = 1.0;
104        pnl.add(tfAccessTokenSecret, gc);
105        SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenSecret);
106        valAccessTokenSecret = new AccessTokenSecretValidator(tfAccessTokenSecret);
107        valAccessTokenSecret.validate();
108        tfAccessTokenSecret.getDocument().addDocumentListener(accessTokenBuilder);
109
110        // the checkbox for saving to preferences
111        gc.gridy = 3;
112        gc.gridx = 0;
113        gc.gridwidth = 2;
114        gc.weightx = 1.0;
115        pnl.add(cbSaveToPreferences, gc);
116        cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
117
118        // filler - grab remaining space
119        gc.gridy = 3;
120        gc.gridx = 0;
121        gc.gridwidth = 2;
122        gc.weightx = 1.0;
123        gc.weighty = 1.0;
124        gc.fill = GridBagConstraints.BOTH;
125        pnl.add(new JPanel(), gc);
126        return pnl;
127    }
128
129    protected JPanel buildTabbedPreferencesPanel() {
130        JPanel pnl = new JPanel(new BorderLayout());
131
132        JTabbedPane tp = new JTabbedPane();
133        tp.add(buildAccessTokenPanel());
134        tp.add(getAdvancedPropertiesPanel());
135
136        tp.setTitleAt(0, tr("Access Token"));
137        tp.setTitleAt(1, tr("Advanced OAuth parameters"));
138
139        tp.setToolTipTextAt(0, tr("Enter the OAuth Access Token"));
140        tp.setToolTipTextAt(1, tr("Enter advanced OAuth properties"));
141
142        pnl.add(tp, BorderLayout.CENTER);
143        return pnl;
144    }
145
146    protected JPanel buildActionsPanel() {
147        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
148        TestAccessTokenAction actTestAccessToken = new TestAccessTokenAction();
149        pnl.add(new JButton(actTestAccessToken));
150        this.addPropertyChangeListener(actTestAccessToken);
151        return pnl;
152    }
153
154    @Override
155    public void setApiUrl(String apiUrl) {
156        super.setApiUrl(apiUrl);
157        if (pnlMessage != null) {
158            pnlMessage.setText(tr("<html><body>"
159                    + "Please enter an OAuth Access Token which is authorized to access the OSM server "
160                    + "''{0}''."
161                    + "</body></html>",
162                    getApiUrl()
163            ));
164        }
165    }
166
167    protected final void build() {
168        setLayout(new BorderLayout());
169        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
170        add(buildTabbedPreferencesPanel(), BorderLayout.CENTER);
171        add(buildActionsPanel(), BorderLayout.SOUTH);
172    }
173
174    @Override
175    public boolean isSaveAccessTokenToPreferences() {
176        return cbSaveToPreferences.isSelected();
177    }
178
179    private static class AccessTokenKeyValidator extends DefaultTextComponentValidator {
180        AccessTokenKeyValidator(JTextComponent tc) {
181            super(tc, tr("Please enter an Access Token Key"),
182                      tr("The Access Token Key must not be empty. Please enter an Access Token Key"));
183        }
184    }
185
186    private static class AccessTokenSecretValidator extends DefaultTextComponentValidator {
187        AccessTokenSecretValidator(JTextComponent tc) {
188            super(tc, tr("Please enter an Access Token Secret"),
189                      tr("The Access Token Secret must not be empty. Please enter an Access Token Secret"));
190        }
191    }
192
193    class AccessTokenBuilder implements DocumentListener {
194
195        public void build() {
196            if (!valAccessTokenKey.isValid() || !valAccessTokenSecret.isValid()) {
197                setAccessToken(null);
198            } else {
199                setAccessToken(new OAuthToken(tfAccessTokenKey.getText().trim(), tfAccessTokenSecret.getText().trim()));
200            }
201        }
202
203        @Override
204        public void changedUpdate(DocumentEvent e) {
205            build();
206        }
207
208        @Override
209        public void insertUpdate(DocumentEvent e) {
210            build();
211        }
212
213        @Override
214        public void removeUpdate(DocumentEvent e) {
215            build();
216        }
217    }
218
219    /**
220     * Action for testing an Access Token
221     */
222    class TestAccessTokenAction extends AbstractAction implements PropertyChangeListener {
223        TestAccessTokenAction() {
224            putValue(NAME, tr("Test Access Token"));
225            new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this);
226            putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token"));
227            updateEnabledState();
228        }
229
230        @Override
231        public void actionPerformed(ActionEvent evt) {
232            TestAccessTokenTask task = new TestAccessTokenTask(
233                    ManualAuthorizationUI.this,
234                    getApiUrl(),
235                    getAdvancedPropertiesPanel().getAdvancedParameters(),
236                    getAccessToken()
237            );
238            executor.execute(task);
239        }
240
241        protected final void updateEnabledState() {
242            setEnabled(hasAccessToken());
243        }
244
245        @Override
246        public void propertyChange(PropertyChangeEvent evt) {
247            if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
248                return;
249            updateEnabledState();
250        }
251    }
252}