hoot.services.models.osm.Element.java Source code

Java tutorial

Introduction

Here is the source code for hoot.services.models.osm.Element.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) 2013, 2014, 2015 DigitalGlobe (http://www.digitalglobe.com/)
 */
package hoot.services.models.osm;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import hoot.services.db.DbUtils;
import hoot.services.db.DbUtils.EntityChangeType;
import hoot.services.db.postgres.PostgresUtils;
import hoot.services.db2.QChangesets;
import hoot.services.db2.QCurrentRelationMembers;
import hoot.services.db2.QUsers;
import hoot.services.geo.BoundingBox;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.xpath.XPathAPI;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.postgresql.util.PGobject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import com.mysema.query.Tuple;
import com.mysema.query.sql.RelationalPathBase;
import com.mysema.query.sql.SQLQuery;
import com.mysema.query.sql.dml.SQLDeleteClause;
import com.mysema.query.types.path.NumberPath;

/**
 * Data common across all OSM elements.  Since all elements are to be serializable to XML and the
 * services database, we implement as much of those related interfaces as possible here.
 */
public abstract class Element implements XmlSerializable, DbSerializable {
    private static final Logger log = LoggerFactory.getLogger(Element.class);

    //order in the enum here is important, since the request diff writer methods use this to determine
    //the order for creating/updating/deleting elements; i.e. create nodes before referencing them
    //in a way, etc.
    public enum ElementType {
        Node, Way, Relation,
        //Technically, changeset doesn't inherit from Element and thus doesn't implement
        //XmlSerializable or DbSerializable, so giving it an element type is a little bit confusing.
        //It helps the code clean up a little bit in places, so leaving as is for now.
        //TODO: Can this be removed?
        Changeset
    }

    /**
     * OSM element type
     */
    protected ElementType elementType;

    public ElementType getElementType() {
        return elementType;
    }

    /**
     * The corresponding changeset ID parsed for this element from the changeset upload request
     */
    private long requestChangesetId = -1;

    public long getRequestChangesetId() {
        return requestChangesetId;
    }

    public void setRequestChangesetId(long id) {
        this.requestChangesetId = id;
    }

    /**
     * a JDBC Connection
     */
    protected Connection conn;

    public Connection getDbConnection() {
        return conn;
    }

    public void setDbConnection(Connection connection) {
        conn = connection;
    }

    protected static DateTimeFormatter timeFormatter;

    public static DateTimeFormatter getTimeFormatter() {
        return timeFormatter;
    }

    /**
     * The element's ID before it is updated by a changeset diff
     */
    protected long oldId;

    public long getOldId() {
        return oldId;
    }

    public void setOldId(long id) {
        oldId = id;
    }

    /*
     * see ChangesetDiffDbWriter::parsedElementIdsToElementsByType
     *
     * This cache is ignored by elements which don't have related element (e.g. Node).
    */
    protected Map<ElementType, Map<Long, Element>> parsedElementIdsToElementsByType;

    public void setElementCache(Map<ElementType, Map<Long, Element>> parsedElementIdsToElementsByType) {
        this.parsedElementIdsToElementsByType = parsedElementIdsToElementsByType;
    }

    /**
     * The associated services database record
     */
    protected Object record;

    public Object getRecord() {
        return record;
    }

    public void setRecord(Object record) throws Exception {
        if (record instanceof Tuple) {
            // This was forced since we are using reflection which need to be refactored to something more solid.

            Tuple tRec = (Tuple) record;
            Object[] tRecs = tRec.toArray();
            if (tRecs.length > 0) {
                // assume first record is good.
                this.record = tRecs[0];
            } else {
                throw new Exception(
                        "Bad Record type. Tuple is empty. Please make sure the first object is tuple is DTO that supports setVersion.");
            }
        } else {
            this.record = record;
        }
    }

    /**
     * Records associated with the contained services database record
     */
    protected Collection<Object> relatedRecords;

    public Collection<Object> getRelatedRecords() {
        return relatedRecords;
    }

    /**
     * ID's of records associated with the contained services database record
     */
    protected Collection<Long> relatedRecordIds;

    /**
     * Changeset diff type being applied to the node: create, modify, or delete
     */
    protected EntityChangeType entityChangeType = EntityChangeType.CREATE;

    public EntityChangeType getEntityChangeType() {
        return entityChangeType;
    }

    public void setEntityChangeType(EntityChangeType changeType) {
        entityChangeType = changeType;
    }

    public Element(Connection conn) {
        this.conn = conn;
        timeFormatter = DateTimeFormat.forPattern(DbUtils.TIMESTAMP_DATE_FORMAT);
    }

    /**
     * Returns the ID of the element associated services database record
     */
    public long getId() throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        return (Long) MethodUtils.invokeMethod(record, "getId", new Object[] {});
    }

    /**
     * Sets the ID of the element associated services database record
     */
    public void setId(long id) throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        MethodUtils.invokeMethod(record, "setId", new Object[] { id });
    }

    //We will keep track of map id internally since we do not have map id column in table any longer
    protected long _mapId = -1;

    /**
     * Returns the map ID of the element's associated services database record
     */
    public long getMapId() throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        //return (Long)MethodUtils.invokeMethod(record, "getMapId", new Object[]{});
        return _mapId;
    }

    /**
     * Sets the map ID of the element associated map
     */
    public void setMapId(long id) throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        //MethodUtils.invokeMethod(record, "setMapId", new Object[]{ id });
        _mapId = id;
    }

    /**
     *  Returns the tags of the element associated services database record
     *
     * @return a string map with tag key/value pairs
     * @throws Exception
     */
    public Map<String, String> getTags() throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables

        Object oTags = MethodUtils.invokeMethod(record, "getTags", new Object[] {});
        if (oTags instanceof PGobject) {
            return PostgresUtils
                    .postgresObjToHStore((PGobject) MethodUtils.invokeMethod(record, "getTags", new Object[] {}));
        }

        return (Map<String, String>) oTags;
    }

    /**
     * Sets tags on an element
     *
     * @param tags string map with tag key/value pairs
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public void setTags(final Map<String, String> tags)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        MethodUtils.invokeMethod(record, "setTags", tags);
    }

    /**
     * Determines whether an element possesses tags beginning with a particular prefix
     *
     * @param startsWithText text to search tags for
     * @return true if the element has one or more tags beginning with startsWithText
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws Exception
     */
    public boolean hasTagsStartingWithText(final String startsWithText)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, Exception {
        final Map<String, String> tags = getTags();
        for (Map.Entry<String, String> tagEntry : tags.entrySet()) {
            if (tagEntry.getKey().startsWith(startsWithText)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the visibility of the element associated services database record
     */
    public boolean getVisible() throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        return (Boolean) MethodUtils.invokeMethod(record, "getVisible", new Object[] {});
    }

    /**
     * Returns the version of the element associated services database record
     */
    public long getVersion() throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        return (Long) MethodUtils.invokeMethod(record, "getVersion", new Object[] {});
    }

    public void setVersion(long version) throws Exception {
        //this is a little risky, but I'm assuming the field probably won't ever change in name
        //in the OSM tables
        MethodUtils.invokeMethod(record, "setVersion", version);
    }

    /**
     * The geospatial bounds for this element
     */
    public abstract BoundingBox getBounds() throws Exception;

    /**
     * Clears temporary data that was the result of parsing the XML to create the element - OPTIONAL
     */
    //TODO: Should this be re-enabled?
    //public void clearTempData() {}

    @Override
    public String toString() {
        final String[] classParts = getClass().getName().split("\\.");
        return getClass().getName().split("\\.")[classParts.length - 1].toLowerCase();
    }

    /**
     * Returns an XML representation of the element
     *
     * @param parentXml XML node this element should be attached under
     * @param modifyingUserId ID of the user which created this element
     * @param modifyingUserDisplayName user display name of the user which created this element
     * @param multiLayerUniqueElementIds if true, ID's are prepended with
     * <map id>_<first letter of the element type>_; this setting activated is not compatible with
     * standard OSM clients (specific to Hootenanny iD)
     * @param addChildren if true, element children are added to the element xml (way nodes for ways
     * relation member for relations); parameter is ignored by the Node element
     * @return an XML element
     */
    public org.w3c.dom.Element toXml(final org.w3c.dom.Element parentXml, final long modifyingUserId,
            final String modifyingUserDisplayName, final boolean multiLayerUniqueElementIds,
            final boolean addChildren) throws Exception {
        Document doc = parentXml.getOwnerDocument();
        org.w3c.dom.Element element = doc.createElement(toString());
        String id = String.valueOf(getId());
        if (multiLayerUniqueElementIds) {
            //hoot custom id unique across map layers
            id = getMapId() + "_" + elementType.toString().toLowerCase().charAt(0) + "_" + id;
        }
        element.setAttribute("id", id);
        element.setAttribute("visible",
                String.valueOf(MethodUtils.invokeMethod(record, "getVisible", new Object[] {})));
        element.setAttribute("version",
                String.valueOf(MethodUtils.invokeMethod(record, "getVersion", new Object[] {})));
        element.setAttribute("changeset",
                String.valueOf(MethodUtils.invokeMethod(record, "getChangesetId", new Object[] {})));
        element.setAttribute("timestamp",
                String.valueOf(MethodUtils.invokeMethod(record, "getTimestamp", new Object[] {})));
        element.setAttribute("user", modifyingUserDisplayName);
        element.setAttribute("uid", String.valueOf(modifyingUserId));
        return element;
    }

    /*
     * This ensures that the changeset ID specified in the element XML is the same as
     * what was specified in the changeset request
     */
    protected long parseChangesetId(final NamedNodeMap xmlAttributes) throws Exception {
        long elementChangesetId = -1;
        try {
            elementChangesetId = Long.parseLong(xmlAttributes.getNamedItem("changeset").getNodeValue());
        } catch (NumberFormatException e) {
        }
        if (elementChangesetId != requestChangesetId) {
            throw new Exception("Invalid changeset ID: " + elementChangesetId + " for " + toString()
                    + ".  Expected changeset ID: " + requestChangesetId);
        }
        return elementChangesetId;
    }

    /*
     * If a new element is being created, it always gets a newly assigned version = 1.  Otherwise, the
     * version passed in the changeset request must match the existing version the server to ensure
     * data integrity.
     */
    protected long parseVersion(final NamedNodeMap xmlAttributes) throws Exception {
        long version = 1;
        //version passed in the request can be ignored if it is a create request
        if (!entityChangeType.equals(EntityChangeType.CREATE)) {
            //if its ever determined that doing this fetch when this method is called in a loop hinders
            //performance (#2951), replace this query with a query outside the loop that checks for the
            //existence of all nodes and validates their versions in a batch query.  The downside to
            //this, however, would be parsing the XML node data more than once.

            Object existingRecord = new SQLQuery(conn, DbUtils.getConfiguration(getMapId())).from(getElementTable())
                    .where(getElementIdField().eq(new Long(oldId))).singleResult(getElementTable());

            if (existingRecord == null) {
                throw new Exception(toString() + " to be updated does not exist with ID: " + oldId);
            }
            version = Long.parseLong(xmlAttributes.getNamedItem("version").getNodeValue());
            //the specified version must be validated with what's already in the database on a modify
            final long existingVersion = (Long) MethodUtils.invokeMethod(existingRecord, "getVersion",
                    new Object[] {});
            if (version != existingVersion) {
                throw new Exception("Invalid version: " + version + " for " + toString() + " with ID: " + getId()
                        + " and version " + existingVersion + " in changeset with ID: "
                        + MethodUtils.invokeMethod(record, "getChangesetId", new Object[] {}));
            }
            version++;
        } else {
            //TODO: I'm not sure how important it is that this check is strictly enforced here, but I'm
            //doing it for now anyway.
            final long parsedVersion = Long.parseLong(xmlAttributes.getNamedItem("version").getNodeValue());
            if (parsedVersion != 0) {
                throw new Exception("Invalid version: " + version + " for element to be created: " + toString()
                        + " with ID: " + getId() + " and version " + parsedVersion + " in changeset with ID: "
                        + MethodUtils.invokeMethod(record, "getChangesetId", new Object[] {}));
            }
        }
        return version;
    }

    //TODO: is this timestamp even actually honored from the xml in the rails port?...don't think so;
    //if not, remove this
    protected Timestamp parseTimestamp(final NamedNodeMap xmlAttributes) {
        Timestamp timestamp = null;
        org.w3c.dom.Node timestampXml = xmlAttributes.getNamedItem("timestamp");
        final Timestamp now = new Timestamp(Calendar.getInstance().getTimeInMillis());
        if (timestampXml != null) {
            try {
                timestamp = new Timestamp(
                        Element.getTimeFormatter().parseDateTime(timestampXml.getNodeValue()).getMillis());
            } catch (IllegalArgumentException e) {
            }
        }
        if (timestamp == null) {
            timestamp = now;
        }
        return timestamp;
    }

    /**
     * Returns an XML representation of the element's data suitable for a changeset upload response
     *
     * It is a little risky to use reflection, but I'm assuming the ID/version fields probably won't
     * ever change in name in the OSM tables.
     *
     * @param parent XML node this element should be attached under
     * @throws Exception
     */
    public org.w3c.dom.Element toChangesetResponseXml(final org.w3c.dom.Element parent) throws Exception {
        Document doc = parent.getOwnerDocument();
        org.w3c.dom.Element entityElement = doc.createElement(toString());
        entityElement.setAttribute("old_id", String.valueOf(getOldId()));
        if (!getEntityChangeType().equals(EntityChangeType.DELETE)) {
            entityElement.setAttribute("new_id",
                    String.valueOf(MethodUtils.invokeMethod(record, "getId", new Object[] {})));
            entityElement.setAttribute("new_version",
                    String.valueOf(MethodUtils.invokeMethod(record, "getVersion", new Object[] {})));
        }
        return entityElement;
    }

    /**
     * Returns a set of elements from the services database
     *
     * @param mapId ID of the map owning the records
     * @param elementType type of elements to be returned
     * @param elementIds ID's of the elements to be returned
     * @param dbConn JDBC Connection
     * @return a set of element records
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static List<?> getElementRecords(final long mapId, final ElementType elementType,
            final Set<Long> elementIds, Connection dbConn) throws InstantiationException, IllegalAccessException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        final Element prototype = ElementFactory.getInstance().create(mapId, elementType, dbConn);

        //SQLQuery query = new SQLQuery(dbConn, DbUtils.getConfiguration());
        if (elementIds.size() > 0) {
            return new SQLQuery(dbConn, DbUtils.getConfiguration(mapId)).from(prototype.getElementTable())
                    .where(prototype.getElementIdField().in(elementIds))
                    .orderBy(prototype.getElementIdField().asc()).list(prototype.getElementTable());
        }

        return new ArrayList();
    }

    /**
     * Returns a set of elements from the services database with user information joined to it
     *
     * @param mapId ID of the map owning the records
     * @param elementType type of elements to be returned
     * @param elementIds ID's of the elements to be returned
     * @param dbConn JDBC Connection
     * @return a set of  element records
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static List<?> getElementRecordsWithUserInfo(final long mapId, final ElementType elementType,
            final Set<Long> elementIds, Connection dbConn) throws InstantiationException, IllegalAccessException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        final Element prototype = ElementFactory.getInstance().create(mapId, elementType, dbConn);

        //SQLQuery query = new SQLQuery(dbConn, DbUtils.getConfiguration());
        QChangesets changesets = QChangesets.changesets;
        QUsers users = QUsers.users;

        if (elementIds.size() > 0) {
            return new SQLQuery(dbConn, DbUtils.getConfiguration("" + mapId)).from(prototype.getElementTable())
                    .join(QChangesets.changesets).on(prototype.getChangesetIdField().eq(changesets.id)).join(users)
                    .on(changesets.userId.eq(users.id)).where(prototype.getElementIdField().in(elementIds))
                    .orderBy(prototype.getElementIdField().asc())
                    .list(prototype.getElementTable(), users, changesets);
        }

        return new ArrayList();
    }

    /**
     * Deletes a set of elements from the services database
     *
     * @param mapId ID of the map owning the elements
     * @param elementType type of elements to be deleted
     * @param elementIds ID's of the elements to be deleted
     * @param dbConn JDBC Connection
     * @throws Exception
     */
    public static void deleteElements(final long mapId, final ElementType elementType, final Set<Long> elementIds,
            Connection dbConn) throws Exception {
        Element prototype = ElementFactory.getInstance().create(mapId, elementType, dbConn);
        RelationalPathBase<?> table = prototype.getElementTable();
        NumberPath<Long> idField = prototype.getElementIdField();

        long numElementsToDelete = 0;

        if (elementIds.size() > 0) {
            numElementsToDelete = new SQLQuery(dbConn, DbUtils.getConfiguration(mapId)).from(table)
                    .join(QChangesets.changesets).where(idField.in(elementIds)).count();
        }

        if (numElementsToDelete != (long) elementIds.size()) {
            throw new Exception("Not all element ID's specified for deletion are valid for element " + "type: "
                    + prototype.toString());
        }
        if (numElementsToDelete > 0) {
            SQLDeleteClause sqldelete = new SQLDeleteClause(dbConn, DbUtils.getConfiguration(mapId), table);

            long result = 0;

            if (elementIds.size() > 0) {
                result = sqldelete.where(idField.in(elementIds)).execute();
            }

            if (result != numElementsToDelete) {
                throw new Exception("Unable to delete elements of type: " + prototype.toString());
            }
        } else {
            log.warn("No elements exist with the specified set of ID's for element type: " + prototype.toString());
        }
    }

    /**
     * Removes all records related (e.g. way nodes for ways, relation members for relations, etc.)
     * to all of the elements with the passed in ID's
     *
     * @param mapId ID of the map owning the elements
     * @param mapIdField services database table map ID field for the join table
     * @param relatedRecordTable services database table for the related record type
     * @param joinField services database table field which joins the related record to the parent
     * record
     * @param elementIds ID's of the elements for which related records are to be deleted
     * @param warnOnNothingRemoved if true, a warning will be logged if no related records were
     * removed
     * @param dbConn JDBC Connection
     */
    public static void removeRelatedRecords(final long mapId, final RelationalPathBase<?> relatedRecordTable,
            final NumberPath<Long> joinField, final Set<Long> elementIds, final boolean warnOnNothingRemoved,
            Connection dbConn) {
        long recordsProcessed = 0;
        if (relatedRecordTable != null && joinField != null) {
            SQLDeleteClause sqldelete = new SQLDeleteClause(dbConn, DbUtils.getConfiguration(mapId),
                    relatedRecordTable);
            recordsProcessed = 0;

            if (elementIds.size() > 0) {
                recordsProcessed = sqldelete.where(joinField.in(elementIds)).execute();
            }

        }
        if (recordsProcessed == 0) {
            if (warnOnNothingRemoved) {
                String msg = "No related records were removed";
                if (relatedRecordTable != null) {
                    msg += " for type: " + relatedRecordTable.toString();
                }
                log.warn(msg);
            }
        } else {
            if (relatedRecordTable != null) {
                log.debug("Removed " + recordsProcessed + " related records for element record type "
                        + relatedRecordTable.toString());
            }
        }
    }

    /**
     * Determines the ID sequence type for an element
     *
     * @param parentElementType OSM element type of the parent of the ID sequence
     * @return an ID sequence type
     * @todo is there a cleaner way to do this?
     */
    /* public static Sequence<Long> getIdSequenceType(final ElementType parentElementType)
     {
       switch (parentElementType)
       {
         case Node:
        
    return Sequences.CURRENT_NODES_ID_SEQ;
        
         case Way:
        
    return Sequences.CURRENT_WAYS_ID_SEQ;
        
         case Relation:
        
    return Sequences.CURRENT_RELATIONS_ID_SEQ;
        
         case Changeset:
        
    return Sequences.CHANGESETS_ID_SEQ;
        
         default:
        
    assert(false);
    return null;
       }
     }*/

    /**
     * Determines whether the specified elements exist in the services database
     *
     * @param mapId ID of the map owning the elements
     * @param elementType the type of element to check existence for
     * @param elementIds a collection of element IDs
     * @param dbConn JDBC Connection
     * @return true if element exist for every input element ID; false otherwise
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static boolean allElementsExist(final long mapId, final ElementType elementType,
            final Set<Long> elementIds, Connection dbConn) throws InstantiationException, IllegalAccessException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException, Exception {
        final Element prototype = ElementFactory.getInstance().create(mapId, elementType, dbConn);

        //SQLQuery query = new SQLQuery(dbConn, DbUtils.getConfiguration());
        if (elementIds.size() > 0) {
            return new SQLQuery(dbConn, DbUtils.getConfiguration(mapId)).from(prototype.getElementTable())
                    .where(prototype.getElementIdField().in(elementIds)).count() == elementIds.size();
        } else {
            return 0 == elementIds.size();
        }

    }

    /**
     * Determines whether all elements in the input list are visible
     *
     * @param mapId ID of the map owning the elements
     * @param elementType the type of element to check visibility for
     * @param elementIds a collection of element IDs
     * @param dbConn JDBC Connection
     * @return true if every node associated with the corresponding input node ID is visible
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static boolean allElementsVisible(final long mapId, final ElementType elementType,
            final Set<Long> elementIds, Connection dbConn) throws InstantiationException, IllegalAccessException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        final Element prototype = ElementFactory.getInstance().create(mapId, elementType, dbConn);

        //SQLQuery query = new SQLQuery(dbConn, DbUtils.getConfiguration());
        if (elementIds.size() > 0) {
            return new SQLQuery(dbConn, DbUtils.getConfiguration(mapId)).from(prototype.getElementTable())
                    .where(prototype.getElementIdField().in(elementIds)
                            .and(prototype.getElementVisibilityField().eq(true)))
                    .count() == elementIds.size();
        } else {
            return 0 == elementIds.size();
        }

    }

    /**
     * Returns the database relation member type given an element type
     *
     * @param elementType the element type for which to retrive the database relation member type
     * @return a database relation member type
     */
    public static DbUtils.nwr_enum elementEnumForElementType(final ElementType elementType) {
        switch (elementType) {
        case Node:

            return DbUtils.nwr_enum.node;

        case Way:

            return DbUtils.nwr_enum.way;

        case Relation:

            return DbUtils.nwr_enum.relation;

        default:

            assert (false);
            return null;
        }
    }

    /**
     * Returns the element type given a database relation member type
     *
     * @param typeEnum a database relation member type
     * @return an element type
     */
    public static ElementType elementTypeForElementEnum(final Object typeEnum) {
        return elementTypeFromString(typeEnum.toString());
    }

    /**
     * Returns an element type given an element type string or element string abbreviation
     *
     * @param elementTypeStr an element type string
     * @return an element type
     */
    public static ElementType elementTypeFromString(final String elementTypeStr) {
        if (!StringUtils.isEmpty(elementTypeStr)) {
            if (elementTypeStr.toLowerCase().equals("node") || elementTypeStr.toLowerCase().equals("n")) {
                return ElementType.Node;
            } else if (elementTypeStr.toLowerCase().equals("way") || elementTypeStr.toLowerCase().equals("w")) {
                return ElementType.Way;
            } else if (elementTypeStr.toLowerCase().equals("relation")
                    || elementTypeStr.toLowerCase().equals("r")) {
                return ElementType.Relation;
            }
        }
        return null;
    }

    /**
     * Returns the database relation member type given an element type
     *
     * @param elementType the element type for which to retrive the database relation member type
     * @return a database relation member type
     */
    public static DbUtils.nwr_enum elementEnumForString(final String elementType) {
        if (elementType.toLowerCase().equals("node")) {
            return DbUtils.nwr_enum.node;
        } else if (elementType.toLowerCase().equals("way")) {
            return DbUtils.nwr_enum.way;
        } else if (elementType.toLowerCase().equals("relation")) {
            return DbUtils.nwr_enum.relation;
        } else {
            assert (false);
            return null;
        }
    }

    /**
     * Returns the ID's of all relations which own this element
     *
     * The ordering of returned records by ID and the use of TreeSet to keep them sorted is only
     * for error reporting readability purposes only.
     *
     * @return sorted list of relation ID's
     * @throws DataAccessException
     * @throws Exception
     */
    protected Set<Long> getOwningRelationIds() throws Exception {

        //SQLQuery query = new SQLQuery(conn, DbUtils.getConfiguration());
        QCurrentRelationMembers currRelMem = QCurrentRelationMembers.currentRelationMembers;

        List<Long> res = new SQLQuery(conn, DbUtils.getConfiguration("" + getMapId())).from(currRelMem)
                .where(currRelMem.memberId.eq(getId())
                        .and(currRelMem.memberType.eq(Element.elementEnumForElementType(elementType))))
                .orderBy(currRelMem.relationId.asc()).list(currRelMem.relationId);

        return new TreeSet<Long>(res);

    }

    /*
     * Parses tags from the element XML and returns them in a map
     */
    protected Map<String, String> parseTags(final org.w3c.dom.Node elementXml) throws Exception {
        Map<String, String> tags = new HashMap<String, String>();
        try {
            NodeList tagXmlNodes = XPathAPI.selectNodeList(elementXml, "tag");
            for (int i = 0; i < tagXmlNodes.getLength(); i++) {
                org.w3c.dom.Node tagXmlNode = tagXmlNodes.item(i);
                NamedNodeMap nodeTagAttributes = tagXmlNode.getAttributes();
                tags.put(nodeTagAttributes.getNamedItem("k").getNodeValue(),
                        nodeTagAttributes.getNamedItem("v").getNodeValue());
            }
        } catch (Exception e) {
            throw new Exception("Error parsing tag.");
        }
        return tags;
    }

    /*
     * Adds tags XML to the parent element XML
     */
    protected org.w3c.dom.Element addTagsXml(final org.w3c.dom.Element elementXml) throws Exception {
        Document doc = elementXml.getOwnerDocument();
        final Map<String, String> tags = getTags();
        if (tags.size() == 0) {
            return null;
        }
        for (Map.Entry<String, String> tagEntry : tags.entrySet()) {
            org.w3c.dom.Element tagElement = doc.createElement("tag");
            tagElement.setAttribute("k", tagEntry.getKey());
            tagElement.setAttribute("v", tagEntry.getValue());
            elementXml.appendChild(tagElement);
        }
        return elementXml;
    }

    /**
     * Gets a total tag count for a specified element type belonging to a specific map
     *
     * @param mapId ID of the map for which to retrieve the tag count
     * @param elementType element type for which to retrieve the tag count
     * @param dbConn JDBC Connection
     * @return a tag count
     * @throws Exception
     */
    public static long getTagCountForElementType(final long mapId, final ElementType elementType, Connection dbConn)
            throws Exception {
        final Element prototype = ElementFactory.getInstance().create(mapId, elementType, dbConn);
        List<?> records = new SQLQuery(dbConn, DbUtils.getConfiguration(mapId)).from(prototype.getElementTable())
                .list(prototype.getElementTable());

        long tagCount = 0;
        for (Object record : records) {

            PGobject tags = (PGobject) MethodUtils.invokeMethod(record, "getTags", new Object[] {});

            if (tags != null) {
                tagCount += PostgresUtils.postgresObjToHStore(tags).size();
            }
        }
        return tagCount;
    }
}