hoot.services.controllers.osm.ElementResource.java Source code

Java tutorial

Introduction

Here is the source code for hoot.services.controllers.osm.ElementResource.java

Source

/*
 * This file is part of Hootenanny.
 *
 * Hootenanny is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * --------------------------------------------------------------------
 *
 * The following copyright notices are generated automatically. If you
 * have a new notice to add, please use the format:
 * " * @copyright Copyright ..."
 * This will properly maintain the copyright information. DigitalGlobe
 * copyrights will be updated automatically.
 *
 * @copyright Copyright (C) 2014, 2015 DigitalGlobe (http://www.digitalglobe.com/)
 */
package hoot.services.controllers.osm;

import java.sql.Connection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import hoot.services.db.DbUtils;
import hoot.services.db2.QMaps;
import hoot.services.db2.QUsers;
import hoot.services.db2.Users;
import hoot.services.models.osm.Element;
import hoot.services.models.osm.Element.ElementType;
import hoot.services.models.osm.ElementFactory;
import hoot.services.models.osm.ModelDaoUtils;
import hoot.services.utils.ResourceErrorHandler;
import hoot.services.utils.XmlDocumentBuilder;
import hoot.services.writers.osm.OsmResponseHeaderGenerator;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.transform.dom.DOMSource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.w3c.dom.Document;

import com.mysema.query.Tuple;

/**
 * Service endpoint for retrieving elements by ID
 */
@Path("/api/0.6")
public class ElementResource {
    private static final Logger log = LoggerFactory.getLogger(ElementResource.class);

    //<map id>_<first letter of the element type>_<element id>
    private static final String UNIQUE_ELEMENT_ID_REGEX = "\\d+_(n|w|r)_\\d+";
    private static final Pattern UNIQUE_ELEMENT_ID_PATTERN = Pattern.compile(UNIQUE_ELEMENT_ID_REGEX);

    @SuppressWarnings("unused")
    private ClassPathXmlApplicationContext appContext;

    public ElementResource() {
        log.debug("Reading application settings...");
        appContext = new ClassPathXmlApplicationContext(new String[] { "db/spring-database.xml" });
    }

    /**
      * <NAME>Element Service - Get Element By ID </NAME>
      * <DESCRIPTION>
      *    Allows for retrieving a node, way, or relation by numeric OSM element ID. Child element of ways
      * and relations are not added to the output (use the "full" method for that functionality).
      * The ID of the map owning the element must be specified in the query string.
      * </DESCRIPTION>
      * <PARAMETERS>
      *  <mapId>
      *  string; ID or name of the map the requested element belongs to
      *  </mapId>
      *  <elementId>
      *   long; OSM ID of the requested element
      *  </elementId>
      *  <elementType>
      *  string; OSM type of the requested element; valid values are "node", "way", or "relation"
      *  </elementType>
      * </PARAMETERS>
      * <OUTPUT>
      *    XML representation of the requested element
      * </OUTPUT>
      * <EXAMPLE>
      *    <URL>http://localhost:8080/hoot-services/osm/api/0.6/node/1?mapId=1</URL>
      *    <REQUEST_TYPE>GET</REQUEST_TYPE>
      *    <INPUT>
      *   </INPUT>
      * <OUTPUT>
      *  OSM XML
      *  see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Element-By-ID
      * </OUTPUT>
      * </EXAMPLE>
     *
     * Returns a single element item's XML for a given map without its element children
     * 
     * @param mapId ID of the map the element belongs to
     * @param elementId OSM element ID of the element to retrieve
     * @param elementType OSM element type of the element to retrieve; valid values are: node, way, 
     * or relation
     * @return element XML document
     * @throws Exception
     * @see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Element-By-ID
     */
    @GET
    @Path("{elementType: node|way|relation}/{elementId}")
    @Consumes({ MediaType.TEXT_PLAIN })
    @Produces({ MediaType.TEXT_XML })
    public Response getElement(@QueryParam("mapId") String mapId, @PathParam("elementId") final long elementId,
            @PathParam("elementType") final String elementType) throws Exception {
        final ElementType elementTypeVal = Element.elementTypeFromString(elementType);
        if (elementTypeVal == null) {
            ResourceErrorHandler.handleError("Invalid element type: " + elementType, Status.BAD_REQUEST, log);
        }

        Connection conn = DbUtils.createConnection();
        Document elementDoc = null;
        try {
            log.debug("Initializing database connection...");

            elementDoc = getElementXml(mapId, elementId, elementTypeVal, false, false, conn);
        } finally {
            DbUtils.closeConnection(conn);
        }

        log.debug("Returning response: " + StringUtils.abbreviate(XmlDocumentBuilder.toString(elementDoc), 100)
                + " ...");
        return Response.ok(new DOMSource(elementDoc), MediaType.APPLICATION_XML)
                .header("Content-type", MediaType.APPLICATION_XML).build();
    }

    /**
      * <NAME>Element Service - Get Element By IDGet Element By Unique ID </NAME>
      * <DESCRIPTION>
      *    Convenience method which allows for retrieving a node, way, or relation by an OSM unique element ID.
      * Child element of ways and relations are not added to the output (use the "full" method for that functionality). The ID of
      * the map owning the element does not need to be specified in the query string because the information already exists in the
      * element ID. This method is not part of the OSM API
      * </DESCRIPTION>
      * <PARAMETERS>
      *  <elementId>
      *   long; OSM ID of the requested element
      *  </elementId>
      * </PARAMETERS>
      * <OUTPUT>
      *    XML representation of the requested element
      * </OUTPUT>
      * <EXAMPLE>
      *    <URL>http://localhost:8080/hoot-services/osm/api/0.6/element/1_n_1</URL>
      *    <REQUEST_TYPE>GET</REQUEST_TYPE>
      *    <INPUT>
      *   </INPUT>
      * <OUTPUT>
      *  OSM XML
      *  see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Element-By-Unique-ID
      * </OUTPUT>
      * </EXAMPLE>
     *
     * Returns a single element item's XML for a given map without its element children
     * 
     * @param elementId a "hoot" formatted element id of the format: 
     * <map id>_<first letter of the element type>_<element id>; valid element types include: node, 
     * way, or relation
     * @return element XML document
     * @throws Exception
     * @see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Element-By-Unique-ID
     */
    @GET
    @Path("/element/{elementId}")
    @Consumes({ MediaType.TEXT_PLAIN })
    @Produces({ MediaType.TEXT_XML })
    public Response getElementByUniqueId(@PathParam("elementId") final String elementId) throws Exception {
        Connection conn = DbUtils.createConnection();
        Document elementDoc = null;
        try {
            log.debug("Initializing database connection...");

            if (!UNIQUE_ELEMENT_ID_PATTERN.matcher(elementId).matches()) {
                ResourceErrorHandler.handleError("Invalid element ID: " + elementId, Status.BAD_REQUEST, log);
            }
            final String[] elementIdParts = elementId.split("_");
            final ElementType elementTypeVal = Element.elementTypeFromString(elementIdParts[1]);
            if (elementTypeVal == null) {
                ResourceErrorHandler.handleError("Invalid element type: " + elementIdParts[1], Status.BAD_REQUEST,
                        log);
            }
            elementDoc = getElementXml(elementIdParts[0], Long.parseLong(elementIdParts[2]), elementTypeVal, true,
                    false, conn);
        } finally {
            DbUtils.closeConnection(conn);
        }

        log.debug("Returning response: " + StringUtils.abbreviate(XmlDocumentBuilder.toString(elementDoc), 100)
                + " ...");
        return Response.ok(new DOMSource(elementDoc), MediaType.APPLICATION_XML)
                .header("Content-type", MediaType.APPLICATION_XML).build();
    }

    /**
      * <NAME>Element Service - Get Full Element By ID </NAME>
      * <DESCRIPTION>
      *    Convenience method which allows for retrieving a way or relation and all of its child elements
      *  (way nodes or relation members) by numeric OSM element ID. The ID of the map owning the element
      *   must be specified in the query string.
      * </DESCRIPTION>
      * <PARAMETERS>
      *  <mapId>
      *  string; ID or name of the map the requested element belongs to
      *  </mapId>
      *  <elementId>
      *   long; OSM ID of the requested element
      *  </elementId>
      *  <elementType>
      *  string; OSM type of the requested element; valid values are "node", "way", or "relation"
      *  </elementType>
      * </PARAMETERS>
      * <OUTPUT>
      *    XML representation of the requested element
      * </OUTPUT>
      * <EXAMPLE>
      *    <URL>http://localhost:8080/hoot-services/osm/api/0.6/node/1/full?mapId=1</URL>
      *    <REQUEST_TYPE>GET</REQUEST_TYPE>
      *    <INPUT>
      *   </INPUT>
      * <OUTPUT>
      *  OSM XML
      *  see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Full-Element-By-ID
      * </OUTPUT>
      * </EXAMPLE>
     *
     * Returns a single element item's XML for a given map with all of its element children
     * 
     * @param mapId ID of the map the element belongs to
     * @param elementId OSM element ID of the element to retrieve
     * @param elementType OSM element type of the element to retrieve; valid values are: way
     * or relation
     * @return element XML document
     * @throws Exception
     * @see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Full-Element-By-ID
     */
    @GET
    @Path("/{elementType: way|relation}/{elementId}/full")
    @Consumes({ MediaType.TEXT_PLAIN })
    @Produces({ MediaType.TEXT_XML })
    public Response getFullElement(@QueryParam("mapId") String mapId, @PathParam("elementId") final long elementId,
            @PathParam("elementType") final String elementType) throws Exception {
        final ElementType elementTypeVal = Element.elementTypeFromString(elementType);
        if (elementTypeVal == null) {
            ResourceErrorHandler.handleError("Invalid element type: " + elementType, Status.BAD_REQUEST, log);
        }

        Connection conn = DbUtils.createConnection();
        Document elementDoc = null;
        try {
            log.debug("Initializing database connection...");

            elementDoc = getElementXml(mapId, elementId, elementTypeVal, false, true, conn);
        } finally {
            DbUtils.closeConnection(conn);
        }

        log.debug("Returning response: " + StringUtils.abbreviate(XmlDocumentBuilder.toString(elementDoc), 100)
                + " ...");
        return Response.ok(new DOMSource(elementDoc), MediaType.APPLICATION_XML)
                .header("Content-type", MediaType.APPLICATION_XML).build();
    }

    /**
      * <NAME>Element Service - Get Full Element By Unique ID </NAME>
      * <DESCRIPTION>
      *    Convenience method which allows for retrieving a way or relation and all of its child
      * elements (way nodes or relation members) by an OSM unique element ID. The ID of the map owning
      * the element does not need to be specified in the query string because the information already exists
      * in the element ID. This method is not part of the OSM API
      * </DESCRIPTION>
      * <PARAMETERS>
      *  <elementId>
      *   string; ID for the requested element unique across all maps of the form:
      *   <map id>_<first letter of the element type>_<element id>; valid values for
      *    <first letter of the element type> are: "w" or "r"
      *  </elementId>
      * </PARAMETERS>
      * <OUTPUT>
      *    XML representation of the requested element
      * </OUTPUT>
      * <EXAMPLE>
      *    <URL>http://localhost:8080/hoot-services/osm/api/0.6/element/1_n_1/full</URL>
      *    <REQUEST_TYPE>GET</REQUEST_TYPE>
      *    <INPUT>
      *   </INPUT>
      * <OUTPUT>
      *  OSM XML
      *  see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Full-Element-By-Unique-ID
      * </OUTPUT>
      * </EXAMPLE>
     *
     * Returns a single element item's XML for a given map with all of its element children
     * 
     * @param elementId a "hoot" formatted element id of the format: 
     * <map id>_<first letter of the element type>_<element id>; valid element types include: 
     * way or relation
     * @return element XML document
     * @throws Exception
     * @see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Full-Element-By-Unique-ID
     */
    @GET
    @Path("/element/{elementId}/full")
    @Consumes({ MediaType.TEXT_PLAIN })
    @Produces({ MediaType.TEXT_XML })
    public Response getFullElementByUniqueId(@PathParam("elementId") final String elementId) throws Exception {
        if (!UNIQUE_ELEMENT_ID_PATTERN.matcher(elementId).matches()) {
            ResourceErrorHandler.handleError("Invalid element ID: " + elementId, Status.BAD_REQUEST, log);
        }
        final String[] elementIdParts = elementId.split("_");
        final ElementType elementType = Element.elementTypeFromString(elementIdParts[1]);
        if (elementType == null) {
            ResourceErrorHandler.handleError("Invalid element type: " + elementType, Status.BAD_REQUEST, log);
        } else if (elementType.equals(ElementType.Node)) {
            ResourceErrorHandler.handleError("Get Full Element Request Invalid for type = Node", Status.BAD_REQUEST,
                    log);
        }

        Connection conn = DbUtils.createConnection();
        Document elementDoc = null;
        try {
            log.debug("Initializing database connection...");

            elementDoc = getElementXml(elementIdParts[0], Long.parseLong(elementIdParts[2]), elementType, true,
                    true, conn);
        } finally {
            DbUtils.closeConnection(conn);
        }

        log.debug("Returning response: " + StringUtils.abbreviate(XmlDocumentBuilder.toString(elementDoc), 100)
                + " ...");
        return Response.ok(new DOMSource(elementDoc), MediaType.APPLICATION_XML)
                .header("Content-type", MediaType.APPLICATION_XML).build();
    }

    private Document getElementXml(final String mapId, final long elementId, final ElementType elementType,
            final boolean multiLayerUniqueElementIds, final boolean addChildren, Connection dbConn)
            throws Exception {
        long mapIdNum = -1;
        try {
            QMaps maps = QMaps.maps;
            //input mapId may be a map ID or a map name
            mapIdNum = ModelDaoUtils.getRecordIdForInputString(mapId, dbConn, maps, maps.id, maps.displayName);
        } catch (Exception e) {
            if (e.getMessage().startsWith("Multiple records exist")) {
                ResourceErrorHandler.handleError(
                        e.getMessage().replaceAll("records", "maps").replaceAll("record", "map"), Status.NOT_FOUND,
                        log);
            } else if (e.getMessage().startsWith("No record exists")) {
                ResourceErrorHandler.handleError(
                        e.getMessage().replaceAll("records", "maps").replaceAll("record", "map"), Status.NOT_FOUND,
                        log);
            }
            ResourceErrorHandler.handleError("Error requesting map with ID: " + mapId + " (" + e.getMessage() + ")",
                    Status.BAD_REQUEST, log);
        }

        Document elementDoc = null;
        Set<Long> elementIds = new HashSet<Long>();
        elementIds.add(elementId);
        @SuppressWarnings("unchecked")
        final List<Tuple> elementRecords = (List<Tuple>) Element.getElementRecordsWithUserInfo(mapIdNum,
                elementType, elementIds, dbConn);
        if (elementRecords == null || elementRecords.size() == 0) {
            ResourceErrorHandler.handleError(
                    "Element with ID: " + elementId + " and type: " + elementType + " does not exist.",
                    Status.NOT_FOUND, log);
        }
        assert (elementRecords.size() == 1);

        final Element element = ElementFactory.getInstance().create(elementType, elementRecords.get(0), dbConn,
                Long.parseLong(mapId));

        Users usersTable = elementRecords.get(0).get(QUsers.users);
        elementDoc = XmlDocumentBuilder.create();
        org.w3c.dom.Element elementRootXml = OsmResponseHeaderGenerator.getOsmDataHeader(elementDoc);
        elementDoc.appendChild(elementRootXml);
        org.w3c.dom.Element elementXml = element.toXml(elementRootXml, usersTable.getId(),
                usersTable.getDisplayName(), multiLayerUniqueElementIds, addChildren);
        elementRootXml.appendChild(elementXml);

        return elementDoc;
    }

    /**
      * <NAME>Element Service - Get Elements By IDs </NAME>
      * <DESCRIPTION>
      *    Allows for retrieving multiple nodes, ways, or relations by numeric OSM element ID. Child element of ways
      * and relations are not added to the output (use the "full" method for that functionality).
      * The ID of the map owning the element must be specified in the query string.
      * </DESCRIPTION>
      * <PARAMETERS>
      *  <mapId>
      *  string; ID or name of the map the requested element belongs to
      *  </mapId>
      *  <elementIds>
      *   string; OSM IDs of the requested elements
      *  </elementIds>
      *  <elementType>
      *  string; OSM type of the requested element; valid values are "node", "way", or "relation"
      *  </elementType>
      * </PARAMETERS>
      * <OUTPUT>
      *    XML representation of the requested element
      * </OUTPUT>
      * <EXAMPLE>
      *    <URL>http://localhost:8080/hoot-services/osm/api/0.6/node/1?mapId=1</URL>
      *    <REQUEST_TYPE>GET</REQUEST_TYPE>
      *    <INPUT>
      *   </INPUT>
      * <OUTPUT>
      *  OSM XML
      *  see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Element-By-ID
      * </OUTPUT>
      * </EXAMPLE>
     *
     * Returns a single element item's XML for a given map without its element children
     * 
     * @param mapId ID of the map the element belongs to
     * @param elementId OSM element ID of the element to retrieve
     * @param elementType OSM element type of the element to retrieve; valid values are: node, way, 
     * or relation
     * @return element XML document
     * @throws Exception
     * @see https://insightcloud.digitalglobe.com/redmine/projects/hootenany/wiki/User_-_OsmElementService#Get-Element-By-ID
     */
    @GET
    @Path("{elementType: nodes|ways|relations}")
    @Consumes({ MediaType.TEXT_PLAIN })
    @Produces({ MediaType.TEXT_XML })
    public Response getElements(@QueryParam("mapId") String mapId,
            @QueryParam("elementIds") final String elementIds, @PathParam("elementType") final String elemType)
            throws Exception {
        String elementType = elemType.substring(0, elemType.length() - 1);
        String[] elemIds = elementIds.split(",");

        final ElementType elementTypeVal = Element.elementTypeFromString(elementType);
        if (elementTypeVal == null) {
            ResourceErrorHandler.handleError("Invalid element type: " + elementType, Status.BAD_REQUEST, log);
        }

        Connection conn = DbUtils.createConnection();
        Document elementDoc = null;
        try {
            log.debug("Initializing database connection...");

            elementDoc = getElementsXml(mapId, elemIds, elementTypeVal, false, false, conn);
        } finally {
            DbUtils.closeConnection(conn);
        }

        log.debug("Returning response: " + StringUtils.abbreviate(XmlDocumentBuilder.toString(elementDoc), 100)
                + " ...");
        return Response.ok(new DOMSource(elementDoc), MediaType.APPLICATION_XML)
                .header("Content-type", MediaType.APPLICATION_XML).build();
    }

    private Document getElementsXml(final String mapId, final String[] elementIdsStr, final ElementType elementType,
            final boolean multiLayerUniqueElementIds, final boolean addChildren, Connection dbConn)
            throws Exception {
        long mapIdNum = -1;
        try {
            QMaps maps = QMaps.maps;
            //input mapId may be a map ID or a map name
            mapIdNum = ModelDaoUtils.getRecordIdForInputString(mapId, dbConn, maps, maps.id, maps.displayName);
        } catch (Exception e) {
            if (e.getMessage().startsWith("Multiple records exist")) {
                ResourceErrorHandler.handleError(
                        e.getMessage().replaceAll("records", "maps").replaceAll("record", "map"), Status.NOT_FOUND,
                        log);
            } else if (e.getMessage().startsWith("No record exists")) {
                ResourceErrorHandler.handleError(
                        e.getMessage().replaceAll("records", "maps").replaceAll("record", "map"), Status.NOT_FOUND,
                        log);
            }
            ResourceErrorHandler.handleError("Error requesting map with ID: " + mapId + " (" + e.getMessage() + ")",
                    Status.BAD_REQUEST, log);
        }

        Document elementDoc = null;
        Set<Long> elementIds = new HashSet<Long>();
        //
        for (String elemId : elementIdsStr) {
            long elementId = Long.parseLong(elemId);
            elementIds.add(elementId);
        }

        @SuppressWarnings("unchecked")
        final List<Tuple> elementRecords = (List<Tuple>) Element.getElementRecordsWithUserInfo(mapIdNum,
                elementType, elementIds, dbConn);
        if (elementRecords == null || elementRecords.size() == 0) {
            ResourceErrorHandler.handleError("Elements with IDs: " + StringUtils.join(elementIdsStr) + " and type: "
                    + elementType + " does not exist.", Status.NOT_FOUND, log);
        }
        assert (elementRecords.size() == 1);

        elementDoc = XmlDocumentBuilder.create();
        org.w3c.dom.Element elementRootXml = OsmResponseHeaderGenerator.getOsmDataHeader(elementDoc);
        elementDoc.appendChild(elementRootXml);

        for (int i = 0; i < elementRecords.size(); i++) {
            final Element element = ElementFactory.getInstance().create(elementType, elementRecords.get(i), dbConn,
                    Long.parseLong(mapId));
            Users usersTable = elementRecords.get(i).get(QUsers.users);

            org.w3c.dom.Element elementXml = element.toXml(elementRootXml, usersTable.getId(),
                    usersTable.getDisplayName(), multiLayerUniqueElementIds, addChildren);
            elementRootXml.appendChild(elementXml);
        }

        return elementDoc;
    }

}