source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java@ 12927

Last change on this file since 12927 was 12803, checked in by Don-vip, 7 years ago

see #15229 - see #15182 - remove GUI references from OsmConnection

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.oauth;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.Font;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Insets;
14import java.awt.event.ActionEvent;
15import java.awt.event.ComponentAdapter;
16import java.awt.event.ComponentEvent;
17import java.awt.event.ItemEvent;
18import java.awt.event.ItemListener;
19import java.awt.event.WindowAdapter;
20import java.awt.event.WindowEvent;
21import java.beans.PropertyChangeEvent;
22import java.beans.PropertyChangeListener;
23import java.lang.reflect.InvocationTargetException;
24import java.net.URL;
25import java.util.concurrent.Executor;
26import java.util.concurrent.FutureTask;
27
28import javax.swing.AbstractAction;
29import javax.swing.BorderFactory;
30import javax.swing.JButton;
31import javax.swing.JDialog;
32import javax.swing.JLabel;
33import javax.swing.JPanel;
34import javax.swing.JScrollPane;
35import javax.swing.SwingUtilities;
36import javax.swing.UIManager;
37import javax.swing.event.HyperlinkEvent;
38import javax.swing.event.HyperlinkListener;
39import javax.swing.text.html.HTMLEditorKit;
40
41import org.openstreetmap.josm.Main;
42import org.openstreetmap.josm.data.Preferences;
43import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
44import org.openstreetmap.josm.data.oauth.OAuthParameters;
45import org.openstreetmap.josm.data.oauth.OAuthToken;
46import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
47import org.openstreetmap.josm.gui.help.HelpUtil;
48import org.openstreetmap.josm.gui.util.GuiHelper;
49import org.openstreetmap.josm.gui.util.WindowGeometry;
50import org.openstreetmap.josm.gui.widgets.HtmlPanel;
51import org.openstreetmap.josm.io.OsmApi;
52import org.openstreetmap.josm.tools.CheckParameterUtil;
53import org.openstreetmap.josm.tools.ImageProvider;
54import org.openstreetmap.josm.tools.InputMapUtils;
55import org.openstreetmap.josm.tools.OpenBrowser;
56import org.openstreetmap.josm.tools.UserCancelException;
57import org.openstreetmap.josm.tools.Utils;
58
59/**
60 * This wizard walks the user to the necessary steps to retrieve an OAuth Access Token which
61 * allows JOSM to access the OSM API on the users behalf.
62 * @since 2746
63 */
64public class OAuthAuthorizationWizard extends JDialog {
65 private boolean canceled;
66 private final String apiUrl;
67
68 private final AuthorizationProcedureComboBox cbAuthorisationProcedure = new AuthorizationProcedureComboBox();
69 private FullyAutomaticAuthorizationUI pnlFullyAutomaticAuthorisationUI;
70 private SemiAutomaticAuthorizationUI pnlSemiAutomaticAuthorisationUI;
71 private ManualAuthorizationUI pnlManualAuthorisationUI;
72 private JScrollPane spAuthorisationProcedureUI;
73 private final transient Executor executor;
74
75 /**
76 * Launches the wizard, {@link OAuthAccessTokenHolder#setAccessToken(OAuthToken) sets the token}
77 * and {@link OAuthAccessTokenHolder#setSaveToPreferences(boolean) saves to preferences}.
78 * @throws UserCancelException if user cancels the operation
79 */
80 public void showDialog() throws UserCancelException {
81 setVisible(true);
82 if (isCanceled()) {
83 throw new UserCancelException();
84 }
85 OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
86 holder.setAccessToken(getAccessToken());
87 holder.setSaveToPreferences(isSaveAccessTokenToPreferences());
88 }
89
90 /**
91 * Builds the row with the action buttons
92 *
93 * @return panel with buttons
94 */
95 protected JPanel buildButtonRow() {
96 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
97
98 AcceptAccessTokenAction actAcceptAccessToken = new AcceptAccessTokenAction();
99 pnlFullyAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken);
100 pnlSemiAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken);
101 pnlManualAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken);
102
103 pnl.add(new JButton(actAcceptAccessToken));
104 pnl.add(new JButton(new CancelAction()));
105 pnl.add(new JButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/OAuthAuthorisationWizard"))));
106
107 return pnl;
108 }
109
110 /**
111 * Builds the panel with general information in the header
112 *
113 * @return panel with information display
114 */
115 protected JPanel buildHeaderInfoPanel() {
116 JPanel pnl = new JPanel(new GridBagLayout());
117 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
118 GridBagConstraints gc = new GridBagConstraints();
119
120 // the oauth logo in the header
121 gc.anchor = GridBagConstraints.NORTHWEST;
122 gc.fill = GridBagConstraints.HORIZONTAL;
123 gc.weightx = 1.0;
124 gc.gridwidth = 2;
125 ImageProvider logoProv = new ImageProvider("oauth", "oauth-logo").setMaxHeight(100);
126 JLabel lbl = new JLabel(logoProv.get());
127 lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
128 lbl.setOpaque(true);
129 pnl.add(lbl, gc);
130
131 // OAuth in a nutshell ...
132 gc.gridy = 1;
133 gc.insets = new Insets(5, 0, 0, 5);
134 HtmlPanel pnlMessage = new HtmlPanel();
135 pnlMessage.setText("<html><body>"
136 + tr("With OAuth you grant JOSM the right to upload map data and GPS tracks "
137 + "on your behalf (<a href=\"{0}\">more info...</a>).", "http://oauth.net/")
138 + "</body></html>"
139 );
140 pnlMessage.getEditorPane().addHyperlinkListener(new ExternalBrowserLauncher());
141 pnl.add(pnlMessage, gc);
142
143 // the authorisation procedure
144 gc.gridy = 2;
145 gc.gridwidth = 1;
146 gc.weightx = 0.0;
147 lbl = new JLabel(tr("Please select an authorization procedure: "));
148 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
149 pnl.add(lbl, gc);
150
151 gc.gridx = 1;
152 gc.gridwidth = 1;
153 gc.weightx = 1.0;
154 pnl.add(cbAuthorisationProcedure, gc);
155 cbAuthorisationProcedure.addItemListener(new AuthorisationProcedureChangeListener());
156 lbl.setLabelFor(cbAuthorisationProcedure);
157
158 if (!OsmApi.DEFAULT_API_URL.equals(apiUrl)) {
159 gc.gridy = 3;
160 gc.gridwidth = 2;
161 gc.gridx = 0;
162 final HtmlPanel pnlWarning = new HtmlPanel();
163 final HTMLEditorKit kit = (HTMLEditorKit) pnlWarning.getEditorPane().getEditorKit();
164 kit.getStyleSheet().addRule(".warning-body {"
165 + "background-color:rgb(253,255,221);padding: 10pt; "
166 + "border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}");
167 kit.getStyleSheet().addRule("ol {margin-left: 1cm}");
168 pnlWarning.setText("<html><body>"
169 + "<p class=\"warning-body\">"
170 + tr("<strong>Warning:</strong> Since you are using not the default OSM API, " +
171 "make sure to set an OAuth consumer key and secret in the <i>Advanced OAuth parameters</i>.")
172 + "</p>"
173 + "</body></html>");
174 pnl.add(pnlWarning, gc);
175 }
176
177 return pnl;
178 }
179
180 /**
181 * Refreshes the view of the authorisation panel, depending on the authorisation procedure
182 * currently selected
183 */
184 protected void refreshAuthorisationProcedurePanel() {
185 AuthorizationProcedure procedure = (AuthorizationProcedure) cbAuthorisationProcedure.getSelectedItem();
186 switch(procedure) {
187 case FULLY_AUTOMATIC:
188 spAuthorisationProcedureUI.getViewport().setView(pnlFullyAutomaticAuthorisationUI);
189 pnlFullyAutomaticAuthorisationUI.revalidate();
190 break;
191 case SEMI_AUTOMATIC:
192 spAuthorisationProcedureUI.getViewport().setView(pnlSemiAutomaticAuthorisationUI);
193 pnlSemiAutomaticAuthorisationUI.revalidate();
194 break;
195 case MANUALLY:
196 spAuthorisationProcedureUI.getViewport().setView(pnlManualAuthorisationUI);
197 pnlManualAuthorisationUI.revalidate();
198 break;
199 }
200 validate();
201 repaint();
202 }
203
204 /**
205 * builds the UI
206 */
207 protected final void build() {
208 getContentPane().setLayout(new BorderLayout());
209 getContentPane().add(buildHeaderInfoPanel(), BorderLayout.NORTH);
210
211 setTitle(tr("Get an Access Token for ''{0}''", apiUrl));
212 this.setMinimumSize(new Dimension(600, 420));
213
214 pnlFullyAutomaticAuthorisationUI = new FullyAutomaticAuthorizationUI(apiUrl, executor);
215 pnlSemiAutomaticAuthorisationUI = new SemiAutomaticAuthorizationUI(apiUrl, executor);
216 pnlManualAuthorisationUI = new ManualAuthorizationUI(apiUrl, executor);
217
218 spAuthorisationProcedureUI = GuiHelper.embedInVerticalScrollPane(new JPanel());
219 spAuthorisationProcedureUI.getVerticalScrollBar().addComponentListener(
220 new ComponentAdapter() {
221 @Override
222 public void componentShown(ComponentEvent e) {
223 spAuthorisationProcedureUI.setBorder(UIManager.getBorder("ScrollPane.border"));
224 }
225
226 @Override
227 public void componentHidden(ComponentEvent e) {
228 spAuthorisationProcedureUI.setBorder(null);
229 }
230 }
231 );
232 getContentPane().add(spAuthorisationProcedureUI, BorderLayout.CENTER);
233 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
234
235 addWindowListener(new WindowEventHandler());
236 InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
237
238 refreshAuthorisationProcedurePanel();
239
240 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/OAuthAuthorisationWizard"));
241 }
242
243 /**
244 * Creates the wizard.
245 *
246 * @param parent the component relative to which the dialog is displayed
247 * @param apiUrl the API URL. Must not be null.
248 * @param executor the executor used for running the HTTP requests for the authorization
249 * @throws IllegalArgumentException if apiUrl is null
250 */
251 public OAuthAuthorizationWizard(Component parent, String apiUrl, Executor executor) {
252 super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
253 CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl");
254 this.apiUrl = apiUrl;
255 this.executor = executor;
256 build();
257 }
258
259 /**
260 * Replies true if the dialog was canceled
261 *
262 * @return true if the dialog was canceled
263 */
264 public boolean isCanceled() {
265 return canceled;
266 }
267
268 protected AbstractAuthorizationUI getCurrentAuthorisationUI() {
269 switch((AuthorizationProcedure) cbAuthorisationProcedure.getSelectedItem()) {
270 case FULLY_AUTOMATIC: return pnlFullyAutomaticAuthorisationUI;
271 case MANUALLY: return pnlManualAuthorisationUI;
272 case SEMI_AUTOMATIC: return pnlSemiAutomaticAuthorisationUI;
273 default: return null;
274 }
275 }
276
277 /**
278 * Replies the Access Token entered using the wizard
279 *
280 * @return the access token. May be null if the wizard was canceled.
281 */
282 public OAuthToken getAccessToken() {
283 return getCurrentAuthorisationUI().getAccessToken();
284 }
285
286 /**
287 * Replies the current OAuth parameters.
288 *
289 * @return the current OAuth parameters.
290 */
291 public OAuthParameters getOAuthParameters() {
292 return getCurrentAuthorisationUI().getOAuthParameters();
293 }
294
295 /**
296 * Replies true if the currently selected Access Token shall be saved to
297 * the preferences.
298 *
299 * @return true if the currently selected Access Token shall be saved to
300 * the preferences
301 */
302 public boolean isSaveAccessTokenToPreferences() {
303 return getCurrentAuthorisationUI().isSaveAccessTokenToPreferences();
304 }
305
306 /**
307 * Initializes the dialog with values from the preferences
308 *
309 */
310 public void initFromPreferences() {
311 // Copy current JOSM preferences to update API url with the one used in this wizard
312 Preferences copyPref = new Preferences(Main.pref);
313 copyPref.put("osm-server.url", apiUrl);
314 pnlFullyAutomaticAuthorisationUI.initFromPreferences(copyPref);
315 pnlSemiAutomaticAuthorisationUI.initFromPreferences(copyPref);
316 pnlManualAuthorisationUI.initFromPreferences(copyPref);
317 }
318
319 @Override
320 public void setVisible(boolean visible) {
321 if (visible) {
322 new WindowGeometry(
323 getClass().getName() + ".geometry",
324 WindowGeometry.centerInWindow(
325 Main.parent,
326 new Dimension(450, 540)
327 )
328 ).applySafe(this);
329 initFromPreferences();
330 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
331 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
332 }
333 super.setVisible(visible);
334 }
335
336 protected void setCanceled(boolean canceled) {
337 this.canceled = canceled;
338 }
339
340 /**
341 * Obtains an OAuth access token for the connection. Afterwards, the token is accessible via {@link OAuthAccessTokenHolder}.
342 * @param serverUrl the URL to OSM server
343 * @throws InterruptedException if we're interrupted while waiting for the event dispatching thread to finish OAuth authorization task
344 * @throws InvocationTargetException if an exception is thrown while running OAuth authorization task
345 * @since 12803
346 */
347 public static void obtainAccessToken(final URL serverUrl) throws InvocationTargetException, InterruptedException {
348 final Runnable authTask = new FutureTask<>(() -> {
349 // Concerning Utils.newDirectExecutor: Main worker cannot be used since this connection is already
350 // executed via main worker. The OAuth connections would block otherwise.
351 final OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
352 Main.parent, serverUrl.toExternalForm(), Utils.newDirectExecutor());
353 wizard.showDialog();
354 return wizard;
355 });
356 // exception handling differs from implementation at GuiHelper.runInEDTAndWait()
357 if (SwingUtilities.isEventDispatchThread()) {
358 authTask.run();
359 } else {
360 SwingUtilities.invokeAndWait(authTask);
361 }
362 }
363
364 class AuthorisationProcedureChangeListener implements ItemListener {
365 @Override
366 public void itemStateChanged(ItemEvent arg0) {
367 refreshAuthorisationProcedurePanel();
368 }
369 }
370
371 class CancelAction extends AbstractAction {
372
373 /**
374 * Constructs a new {@code CancelAction}.
375 */
376 CancelAction() {
377 putValue(NAME, tr("Cancel"));
378 new ImageProvider("cancel").getResource().attachImageIcon(this);
379 putValue(SHORT_DESCRIPTION, tr("Close the dialog and cancel authorization"));
380 }
381
382 public void cancel() {
383 setCanceled(true);
384 setVisible(false);
385 }
386
387 @Override
388 public void actionPerformed(ActionEvent evt) {
389 cancel();
390 }
391 }
392
393 class AcceptAccessTokenAction extends AbstractAction implements PropertyChangeListener {
394
395 /**
396 * Constructs a new {@code AcceptAccessTokenAction}.
397 */
398 AcceptAccessTokenAction() {
399 putValue(NAME, tr("Accept Access Token"));
400 new ImageProvider("ok").getResource().attachImageIcon(this);
401 putValue(SHORT_DESCRIPTION, tr("Close the dialog and accept the Access Token"));
402 updateEnabledState(null);
403 }
404
405 @Override
406 public void actionPerformed(ActionEvent evt) {
407 setCanceled(false);
408 setVisible(false);
409 }
410
411 public final void updateEnabledState(OAuthToken token) {
412 setEnabled(token != null);
413 }
414
415 @Override
416 public void propertyChange(PropertyChangeEvent evt) {
417 if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
418 return;
419 updateEnabledState((OAuthToken) evt.getNewValue());
420 }
421 }
422
423 class WindowEventHandler extends WindowAdapter {
424 @Override
425 public void windowClosing(WindowEvent e) {
426 new CancelAction().cancel();
427 }
428 }
429
430 static class ExternalBrowserLauncher implements HyperlinkListener {
431 @Override
432 public void hyperlinkUpdate(HyperlinkEvent e) {
433 if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
434 OpenBrowser.displayUrl(e.getDescription());
435 }
436 }
437 }
438}
Note: See TracBrowser for help on using the repository browser.