001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset.query;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.io.IOException;
008import java.lang.reflect.InvocationTargetException;
009import java.util.HashSet;
010import java.util.Set;
011
012import javax.swing.JOptionPane;
013import javax.swing.SwingUtilities;
014
015import org.openstreetmap.josm.Main;
016import org.openstreetmap.josm.data.osm.Changeset;
017import org.openstreetmap.josm.data.osm.ChangesetCache;
018import org.openstreetmap.josm.data.osm.UserInfo;
019import org.openstreetmap.josm.gui.JosmUserIdentityManager;
020import org.openstreetmap.josm.gui.PleaseWaitRunnable;
021import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetDownloadTask;
022import org.openstreetmap.josm.gui.util.GuiHelper;
023import org.openstreetmap.josm.io.ChangesetQuery;
024import org.openstreetmap.josm.io.OsmServerChangesetReader;
025import org.openstreetmap.josm.io.OsmServerUserInfoReader;
026import org.openstreetmap.josm.io.OsmTransferCanceledException;
027import org.openstreetmap.josm.io.OsmTransferException;
028import org.openstreetmap.josm.tools.BugReportExceptionHandler;
029import org.openstreetmap.josm.tools.CheckParameterUtil;
030import org.openstreetmap.josm.tools.ExceptionUtil;
031import org.xml.sax.SAXException;
032
033/**
034 * Asynchronous task to send a changeset query to the OSM API.
035 * @since 2689
036 */
037public class ChangesetQueryTask extends PleaseWaitRunnable implements ChangesetDownloadTask {
038
039    /** the changeset query */
040    private ChangesetQuery query;
041    /** true if the task was canceled */
042    private boolean canceled;
043    /** the set of downloaded changesets */
044    private Set<Changeset> downloadedChangesets;
045    /** the last exception remembered, if any */
046    private Exception lastException;
047    /** the reader object used to read information about the current user from the API */
048    private OsmServerUserInfoReader userInfoReader;
049    /** the reader object used to submit the changeset query to the API */
050    private OsmServerChangesetReader changesetReader;
051
052    /**
053     * Creates the task.
054     *
055     * @param query the query to submit to the OSM server. Must not be null.
056     * @throws IllegalArgumentException if query is null.
057     */
058    public ChangesetQueryTask(ChangesetQuery query) {
059        super(tr("Querying and downloading changesets"), false /* don't ignore exceptions */);
060        CheckParameterUtil.ensureParameterNotNull(query, "query");
061        this.query = query;
062    }
063
064    /**
065     * Creates the task.
066     *
067     * @param parent the parent component relative to which the {@link org.openstreetmap.josm.gui.PleaseWaitDialog} is displayed.
068     * Must not be null.
069     * @param query the query to submit to the OSM server. Must not be null.
070     * @throws IllegalArgumentException if query is null.
071     * @throws IllegalArgumentException if parent is null
072     */
073    public ChangesetQueryTask(Component parent, ChangesetQuery query) {
074        super(parent, tr("Querying and downloading changesets"), false /* don't ignore exceptions */);
075        CheckParameterUtil.ensureParameterNotNull(query, "query");
076        this.query = query;
077    }
078
079    @Override
080    protected void cancel() {
081        canceled = true;
082        synchronized (this) {
083            if (userInfoReader != null) {
084                userInfoReader.cancel();
085            }
086        }
087        synchronized (this) {
088            if (changesetReader != null) {
089                changesetReader.cancel();
090            }
091        }
092    }
093
094    @Override
095    protected void finish() {
096        if (canceled) return;
097        if (lastException != null) {
098            GuiHelper.runInEDTAndWait(new Runnable() {
099                private final Component parent = progressMonitor != null ? progressMonitor.getWindowParent() : null;
100                @Override
101                public void run() {
102                    JOptionPane.showMessageDialog(
103                            parent != null ? parent : Main.parent,
104                            ExceptionUtil.explainException(lastException),
105                            tr("Errors during download"),
106                            JOptionPane.ERROR_MESSAGE);
107                }
108            });
109            return;
110        }
111
112        // update the global changeset cache with the downloaded changesets.
113        // this will trigger change events which views are listening to. They
114        // will update their views accordingly.
115        //
116        // Run on the EDT because UI updates are triggered.
117        //
118        Runnable r = new Runnable() {
119            @Override public void run() {
120                ChangesetCache.getInstance().update(downloadedChangesets);
121            }
122        };
123        if (SwingUtilities.isEventDispatchThread()) {
124            r.run();
125        } else {
126            try {
127                SwingUtilities.invokeAndWait(r);
128            } catch (InterruptedException e) {
129                Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
130            } catch (InvocationTargetException e) {
131                Throwable t = e.getTargetException();
132                if (t instanceof RuntimeException) {
133                    BugReportExceptionHandler.handleException(t);
134                } else if (t instanceof Exception) {
135                    ExceptionUtil.explainException(e);
136                } else {
137                    BugReportExceptionHandler.handleException(t);
138                }
139            }
140        }
141    }
142
143    /**
144     * Tries to fully identify the current JOSM user
145     *
146     * @throws OsmTransferException if something went wrong
147     */
148    protected void fullyIdentifyCurrentUser() throws OsmTransferException {
149        getProgressMonitor().indeterminateSubTask(tr("Determine user id for current user..."));
150
151        synchronized (this) {
152            userInfoReader = new OsmServerUserInfoReader();
153        }
154        UserInfo info = userInfoReader.fetchUserInfo(getProgressMonitor().createSubTaskMonitor(1, false));
155        synchronized (this) {
156            userInfoReader = null;
157        }
158        JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
159        im.setFullyIdentified(im.getUserName(), info);
160    }
161
162    @Override
163    protected void realRun() throws SAXException, IOException, OsmTransferException {
164        try {
165            JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
166            if (query.isRestrictedToPartiallyIdentifiedUser() && im.isCurrentUser(query.getUserName())) {
167                // if we query changesets for the current user, make sure we query against
168                // its user id, not its user name. If necessary, determine the user id first.
169                //
170                if (im.isPartiallyIdentified()) {
171                    fullyIdentifyCurrentUser();
172                }
173                query = query.forUser(JosmUserIdentityManager.getInstance().getUserId());
174            }
175            if (canceled) return;
176            getProgressMonitor().indeterminateSubTask(tr("Query and download changesets ..."));
177            synchronized (this) {
178                changesetReader = new OsmServerChangesetReader();
179            }
180            downloadedChangesets = new HashSet<>();
181            downloadedChangesets.addAll(changesetReader.queryChangesets(query, getProgressMonitor().createSubTaskMonitor(0, false)));
182            synchronized (this) {
183                changesetReader = null;
184            }
185        } catch (OsmTransferCanceledException e) {
186            // thrown if user cancel the authentication dialog
187            canceled = true;
188        }  catch (OsmTransferException e) {
189            if (canceled)
190                return;
191            this.lastException = e;
192        }
193    }
194
195    /* ------------------------------------------------------------------------------- */
196    /* interface ChangesetDownloadTask                                                 */
197    /* ------------------------------------------------------------------------------- */
198    @Override
199    public Set<Changeset> getDownloadedChangesets() {
200        return downloadedChangesets;
201    }
202
203    @Override
204    public boolean isCanceled() {
205        return canceled;
206    }
207
208    @Override
209    public boolean isFailed() {
210        return lastException != null;
211    }
212}