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.List;
008import java.util.stream.Collectors;
009import java.util.stream.IntStream;
010
011import javax.xml.xpath.XPath;
012import javax.xml.xpath.XPathConstants;
013import javax.xml.xpath.XPathException;
014import javax.xml.xpath.XPathFactory;
015
016import org.openstreetmap.josm.data.coor.LatLon;
017import org.openstreetmap.josm.data.osm.DataSet;
018import org.openstreetmap.josm.data.osm.UserInfo;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020import org.openstreetmap.josm.tools.UncheckedParseException;
021import org.openstreetmap.josm.tools.XmlParsingException;
022import org.openstreetmap.josm.tools.date.DateUtils;
023import org.w3c.dom.Document;
024import org.w3c.dom.Node;
025import org.w3c.dom.NodeList;
026
027/**
028 * Download and parse info of the logged in user (OSM API v0.6 "/user/details").
029 * @see <a href="https://wiki.openstreetmap.org/wiki/API_v0.6#Details_of_the_logged-in_user">/user/details</a>
030 */
031public class OsmServerUserInfoReader extends OsmServerReader {
032
033    /**
034     * Parses the given XML data and returns the associated user info.
035     * @param document The XML contents
036     * @return The user info
037     * @throws XmlParsingException if parsing goes wrong
038     */
039    public static UserInfo buildFromXML(Document document) throws XmlParsingException {
040        try {
041            XPathFactory factory = XPathFactory.newInstance();
042            XPath xpath = factory.newXPath();
043            UserInfo userInfo = new UserInfo();
044            Node xmlNode = (Node) xpath.compile("/osm/user[1]").evaluate(document, XPathConstants.NODE);
045            if (xmlNode == null)
046                throw new XmlParsingException(tr("XML tag <user> is missing."));
047
048            // -- id
049            String v = getAttribute(xmlNode, "id");
050            if (v == null)
051                throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "id", "user"));
052            try {
053                userInfo.setId(Integer.parseInt(v));
054            } catch (NumberFormatException e) {
055                throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.", "id", "user", v), e);
056            }
057            // -- display name
058            v = getAttribute(xmlNode, "display_name");
059            userInfo.setDisplayName(v);
060            // -- account_created
061            v = getAttribute(xmlNode, "account_created");
062            if (v != null) {
063                userInfo.setAccountCreated(DateUtils.parseInstant(v));
064            }
065            // -- description
066            xmlNode = (Node) xpath.compile("/osm/user[1]/description[1]/text()").evaluate(document, XPathConstants.NODE);
067            if (xmlNode != null) {
068                userInfo.setDescription(xmlNode.getNodeValue());
069            }
070            // -- home
071            xmlNode = (Node) xpath.compile("/osm/user[1]/home").evaluate(document, XPathConstants.NODE);
072            if (xmlNode != null) {
073                v = getAttribute(xmlNode, "lat");
074                if (v == null)
075                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "lat", "home"));
076                double lat;
077                try {
078                    lat = Double.parseDouble(v);
079                } catch (NumberFormatException e) {
080                    throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.",
081                            "lat", "home", v), e);
082                }
083
084                v = getAttribute(xmlNode, "lon");
085                if (v == null)
086                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "lon", "home"));
087                double lon;
088                try {
089                    lon = Double.parseDouble(v);
090                } catch (NumberFormatException e) {
091                    throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.",
092                            "lon", "home", v), e);
093                }
094
095                v = getAttribute(xmlNode, "zoom");
096                if (v == null)
097                    throw new XmlParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "zoom", "home"));
098                int zoom;
099                try {
100                    zoom = Integer.parseInt(v);
101                } catch (NumberFormatException e) {
102                    throw new XmlParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.",
103                            "zoom", "home", v), e);
104                }
105                userInfo.setHome(new LatLon(lat, lon));
106                userInfo.setHomeZoom(zoom);
107            }
108
109            // -- language list
110            NodeList xmlNodeList = (NodeList) xpath.compile("/osm/user[1]/languages[1]/lang/text()").evaluate(document, XPathConstants.NODESET);
111            if (xmlNodeList != null) {
112                List<String> languages = IntStream.range(0, xmlNodeList.getLength())
113                        .mapToObj(i -> xmlNodeList.item(i).getNodeValue())
114                        .collect(Collectors.toList());
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}