001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.time.DateTimeException;
007import java.util.LinkedList;
008import java.util.List;
009
010import javax.xml.xpath.XPath;
011import javax.xml.xpath.XPathConstants;
012import javax.xml.xpath.XPathException;
013import javax.xml.xpath.XPathFactory;
014
015import org.openstreetmap.josm.data.coor.LatLon;
016import org.openstreetmap.josm.data.osm.DataSet;
017import org.openstreetmap.josm.data.osm.UserInfo;
018import org.openstreetmap.josm.gui.progress.ProgressMonitor;
019import org.openstreetmap.josm.tools.UncheckedParseException;
020import org.openstreetmap.josm.tools.XmlParsingException;
021import org.openstreetmap.josm.tools.date.DateUtils;
022import org.w3c.dom.Document;
023import org.w3c.dom.Node;
024import org.w3c.dom.NodeList;
025
026/**
027 * Download and parse info of the logged in user (OSM API v0.6 "/user/details").
028 * @see <a href="https://wiki.openstreetmap.org/wiki/API_v0.6#Details_of_the_logged-in_user">/user/details</a>
029 */
030public class OsmServerUserInfoReader extends OsmServerReader {
031
032    /**
033     * Parses the given XML data and returns the associated user info.
034     * @param document The XML contents
035     * @return The user info
036     * @throws XmlParsingException if parsing goes wrong
037     */
038    public static UserInfo buildFromXML(Document document) throws XmlParsingException {
039        try {
040            XPathFactory factory = XPathFactory.newInstance();
041            XPath xpath = factory.newXPath();
042            UserInfo userInfo = new UserInfo();
043            Node xmlNode = (Node) xpath.compile("/osm/user[1]").evaluate(document, XPathConstants.NODE);
044            if (xmlNode == null)
045                throw new XmlParsingException(tr("XML tag <user> is missing."));
046
047            // -- id
048            String v = getAttribute(xmlNode, "id");
049            if (v == null)
050                throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "id", "user"));
051            try {
052                userInfo.setId(Integer.parseInt(v));
053            } catch (NumberFormatException e) {
054                throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.", "id", "user", v), e);
055            }
056            // -- display name
057            v = getAttribute(xmlNode, "display_name");
058            userInfo.setDisplayName(v);
059            // -- account_created
060            v = getAttribute(xmlNode, "account_created");
061            if (v != null) {
062                userInfo.setAccountCreated(DateUtils.fromString(v));
063            }
064            // -- description
065            xmlNode = (Node) xpath.compile("/osm/user[1]/description[1]/text()").evaluate(document, XPathConstants.NODE);
066            if (xmlNode != null) {
067                userInfo.setDescription(xmlNode.getNodeValue());
068            }
069            // -- home
070            xmlNode = (Node) xpath.compile("/osm/user[1]/home").evaluate(document, XPathConstants.NODE);
071            if (xmlNode != null) {
072                v = getAttribute(xmlNode, "lat");
073                if (v == null)
074                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "lat", "home"));
075                double lat;
076                try {
077                    lat = Double.parseDouble(v);
078                } catch (NumberFormatException e) {
079                    throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.",
080                            "lat", "home", v), e);
081                }
082
083                v = getAttribute(xmlNode, "lon");
084                if (v == null)
085                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "lon", "home"));
086                double lon;
087                try {
088                    lon = Double.parseDouble(v);
089                } catch (NumberFormatException e) {
090                    throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.",
091                            "lon", "home", v), e);
092                }
093
094                v = getAttribute(xmlNode, "zoom");
095                if (v == null)
096                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "zoom", "home"));
097                int zoom;
098                try {
099                    zoom = Integer.parseInt(v);
100                } catch (NumberFormatException e) {
101                    throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.",
102                            "zoom", "home", v), e);
103                }
104                userInfo.setHome(new LatLon(lat, lon));
105                userInfo.setHomeZoom(zoom);
106            }
107
108            // -- language list
109            NodeList xmlNodeList = (NodeList) xpath.compile("/osm/user[1]/languages[1]/lang/text()").evaluate(document, XPathConstants.NODESET);
110            if (xmlNodeList != null) {
111                List<String> languages = new LinkedList<>();
112                for (int i = 0; i < xmlNodeList.getLength(); i++) {
113                    languages.add(xmlNodeList.item(i).getNodeValue());
114                }
115                userInfo.setLanguages(languages);
116            }
117
118            // -- messages
119            xmlNode = (Node) xpath.compile("/osm/user[1]/messages/received").evaluate(document, XPathConstants.NODE);
120            if (xmlNode != null) {
121                v = getAttribute(xmlNode, "unread");
122                if (v == null)
123                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "unread", "received"));
124                try {
125                    userInfo.setUnreadMessages(Integer.parseInt(v));
126                } catch (NumberFormatException e) {
127                    throw new XmlParsingException(
128                            tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.", "unread", "received", v), e);
129                }
130            }
131
132            return userInfo;
133        } catch (XPathException | UncheckedParseException | DateTimeException e) {
134            throw new XmlParsingException(e);
135        }
136    }
137
138    /**
139     * Constructs a new {@code OsmServerUserInfoReader}.
140     */
141    public OsmServerUserInfoReader() {
142        setDoAuthenticate(true);
143    }
144
145    @Override
146    public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
147        // not implemented
148        return null;
149    }
150
151    /**
152     * Fetches user info, without explicit reason.
153     * @param monitor The progress monitor
154     * @return The user info
155     * @throws OsmTransferException if something goes wrong
156     */
157    public UserInfo fetchUserInfo(ProgressMonitor monitor) throws OsmTransferException {
158        return fetchUserInfo(monitor, null);
159    }
160
161    /**
162     * Fetches user info, with an explicit reason.
163     * @param monitor The progress monitor
164     * @param reason The reason to show on console. Can be {@code null} if no reason is given
165     * @return The user info
166     * @throws OsmTransferException if something goes wrong
167     * @since 6695
168     */
169    public UserInfo fetchUserInfo(ProgressMonitor monitor, String reason) throws OsmTransferException {
170        return fetchData("user/details", tr("Reading user info ..."),
171                OsmServerUserInfoReader::buildFromXML, monitor, reason);
172    }
173}