hoot.services.writers.review.ReviewPrepareDbWriter2.java Source code

Java tutorial

Introduction

Here is the source code for hoot.services.writers.review.ReviewPrepareDbWriter2.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) 2015 DigitalGlobe (http://www.digitalglobe.com/)
 */
package hoot.services.writers.review;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.postgresql.util.PGobject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ArrayListMultimap;
import com.mysema.query.sql.SQLQuery;

import hoot.services.db.DbUtils;
import hoot.services.db.postgres.PostgresUtils;
import hoot.services.db2.ElementIdMappings;
import hoot.services.db2.ReviewItems;
import hoot.services.models.osm.Element.ElementType;

/**
 * Writes review data to the services database
 * 
 * This is an attempt to resolve the handling of fuzzy matches (see #6191).
 * 
 * @todo If this code is permanently usable, then the unit tests need to be rewritten for it.
 */
public class ReviewPrepareDbWriter2 extends ReviewPrepareDbWriter {
    private static final Logger log = LoggerFactory.getLogger(ReviewPrepareDbWriter2.class);

    public ReviewPrepareDbWriter2() throws Exception {
        super();
    }

    /*
     * logging records with invalid uuid's and skipping; if errors should be thrown, then the
     * unit tests will have to reworked
     */
    @Override
    protected boolean parseElementUniqueIdTags(final long mapId) throws Exception {
        final String logMsgStart = "Parsing element unique ID tags for map with ID: " + mapId + ".  Step 2 of 4.";
        log.info(logMsgStart);

        uniqueIdsParsed = 0;
        idMappingRecordWritten = false;
        List<ElementIdMappings> elementIdMappingRecordsToInsert = new ArrayList<ElementIdMappings>();
        //create this outside of the batch read loop, since we need to maintain a list of unique
        //ID's parsed over the entire map's set of reviewable records
        Set<String> elementIds = new HashSet<String>();

        for (ElementType elementType : ElementType.values()) {
            if (!elementType.equals(ElementType.Changeset)) {
                //final Element prototype = ElementFactory.getInstance().create(elementType, conn);

                int numElementsReturned = Integer.MAX_VALUE;
                int elementIndex = 0;
                while (numElementsReturned > 0) {
                    //get all reviewable elements
                    final Map<Long, Object> reviewableElementRecords = getParseableElementRecords(mapId,
                            elementType, maxRecordSelectSize, elementIndex);
                    numElementsReturned = reviewableElementRecords.size();
                    elementIndex += numElementsReturned;
                    for (Map.Entry<Long, Object> reviewableElementRecordEntry : reviewableElementRecords
                            .entrySet()) {
                        final long osmElementId = reviewableElementRecordEntry.getKey();
                        final Object reviewableElementRecord = reviewableElementRecordEntry.getValue();
                        final Map<String, String> tags = PostgresUtils.postgresObjToHStore((PGobject) MethodUtils
                                .invokeMethod(reviewableElementRecord, "getTags", new Object[] {}));
                        final String uniqueElementIdStr = StringUtils.trimToNull(tags.get("uuid"));
                        if (uniqueElementIdStr == null) {
                            log.warn("Invalid UUID: " + uniqueElementIdStr + " for map with ID: " + mapId
                                    + ".  Skipping adding unique ID record...");
                        } else {
                            //In the case of the fuzzy match conflict example, this ID may be made up of multiple
                            //parts.  Treat each ID part separately.  TODO: add test for this case
                            String[] uniqueElementIds = null;
                            if (uniqueElementIdStr.contains(";")) {
                                log.debug("Multiple part UUID...");
                                uniqueElementIds = uniqueElementIdStr.split(";");
                            } else {
                                uniqueElementIds = new String[1];
                                uniqueElementIds[0] = uniqueElementIdStr;
                            }

                            for (String uniqueElementId : uniqueElementIds) {
                                //TODO: make this a batch query somehow
                                if (new SQLQuery(conn, DbUtils.getConfiguration(mapId)).from(elementIdMappings)
                                        .where(elementIdMappings.mapId.eq(mapId)
                                                .and(elementIdMappings.elementId.eq(uniqueElementId)))
                                        .count() > 0) {
                                    log.warn("UUID: " + uniqueElementId + " for map with ID: " + mapId
                                            + " already exists.  " + "Skipping adding unique ID record...");
                                } else {
                                    if (elementIds.add(uniqueElementId)) //don't add duplicates
                                    {
                                        log.debug("Adding UUID: " + uniqueElementId);
                                        elementIdMappingRecordsToInsert.add(createElementIdMappingRecord(
                                                uniqueElementId, osmElementId, elementType, mapId));
                                        flushIdMappingRecords(elementIdMappingRecordsToInsert, maxRecordBatchSize,
                                                logMsgStart);
                                    } else {
                                        log.warn("Duplicate element ID: " + uniqueElementId.toString()
                                                + " for map with ID: " + mapId
                                                + ".  Skipping adding unique ID record...");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        //final flush
        flushIdMappingRecords(elementIdMappingRecordsToInsert, 0, logMsgStart);

        log.debug("Wrote " + elementIds.size() + " ID mappings.");

        return idMappingRecordWritten;
    }

    /*
     * logging records with invalid tag values and skipping; if errors should be thrown, then the
     * unit tests will have to reworked
     *
     * @todo What's a good way to write job completion percentage to the db from here?  This runs in
     * a transaction, so it won't show up for external job pollers until all the parsing is done.
     */
    @Override
    protected boolean parseElementReviewTags(final long mapId) throws Exception {
        final String logMsgStart = "Parsing element review tags for map with ID: " + mapId + ".  Step 3 of 4.";
        log.info(logMsgStart);
        //SQLQuery query = new SQLQuery(conn, DbUtils.getConfiguration());
        //parse review tags for all nodes, ways, and relations from the OSM element tables for the given
        //map
        reviewRecordsParsed = 0;
        reviewRecordWritten = false;
        List<ReviewItems> reviewRecordsToInsert = new ArrayList<ReviewItems>();
        //create this outside of the batch read loop, since we need to maintain a list of unique
        //ID's parsed over the entire map's set of reviewable records
        reviewableItemIdToReviewAgainstItemIds = ArrayListMultimap.create();
        reviewableItemIdToReviewAgainstItemIds.putAll(previouslyReviewedItemIdToReviewAgainstItemIds);

        int numReviewItemsAdded = 0;
        for (ElementType elementType : ElementType.values()) {
            if (!elementType.equals(ElementType.Changeset)) {
                //final Element prototype = ElementFactory.getInstance().create(elementType, conn);

                int numElementsReturned = Integer.MAX_VALUE;
                int elementIndex = 0;
                while (numElementsReturned > 0) {
                    //get a batch of reviewable elements
                    final Map<Long, Object> reviewableElementRecords = getReviewableElementRecords(mapId,
                            elementType, maxRecordSelectSize, elementIndex);
                    numElementsReturned = reviewableElementRecords.size();
                    elementIndex += numElementsReturned;
                    for (Map.Entry<Long, Object> reviewableElementRecordEntry : reviewableElementRecords
                            .entrySet()) {
                        final Object reviewableElementRecord = reviewableElementRecordEntry.getValue();
                        final Map<String, String> tags = PostgresUtils.postgresObjToHStore((PGobject) MethodUtils
                                .invokeMethod(reviewableElementRecord, "getTags", new Object[] {}));
                        final String reviewableItemIdStr = StringUtils.trimToNull(tags.get("uuid"));
                        if (StringUtils.isEmpty(reviewableItemIdStr)) {
                            log.warn("Invalid UUID: " + reviewableItemIdStr + " for map with ID: " + mapId
                                    + " Skipping adding review record...");
                        } else {
                            //In the case of the fuzzy match conflict example, this ID may be made up of multiple
                            //parts.  Treat each ID part separately.  TODO: add test for this case
                            String[] reviewableItemIds = null;
                            if (reviewableItemIdStr.contains(";")) {
                                log.debug("Multiple part reviewable item ID...");
                                reviewableItemIds = reviewableItemIdStr.split(";");
                            } else {
                                reviewableItemIds = new String[1];
                                reviewableItemIds[0] = reviewableItemIdStr;
                            }

                            for (String reviewableItemId : reviewableItemIds) {
                                //some items won't have a review score tag; For now, the way this is being handled
                                //is that items missing a tag get a review score = 1.0; items with an empty string
                                //or invalid string for a review tag get a review score of -1.0, which invalidates
                                //the review pair.  The case could be argued that invalid/empty score strings should
                                //also result in a review score = 1.0.
                                double reviewScore = -1.0;
                                if (tags.containsKey("hoot:review:score")) {
                                    try {
                                        reviewScore = Double.parseDouble(tags.get("hoot:review:score"));
                                    } catch (NumberFormatException e) {
                                    }
                                } else {
                                    reviewScore = 1.0;
                                }

                                if (reviewScore != -1.0 && tags.containsKey("hoot:review:uuid")
                                        && StringUtils.trimToNull(tags.get("hoot:review:uuid")) != null) {
                                    String[] reviewAgainstItemIds = null;
                                    final String itemsToReviewAgainstStr = StringUtils
                                            .trimToNull(tags.get("hoot:review:uuid"));
                                    //We are parsing pairwise comparisons and don't want duplicates, so ignore one
                                    //to many reviewable item to review against item relationships.  They are always
                                    //represented with a duplicated one to one relationship in the data.
                                    if (!itemsToReviewAgainstStr.contains(";")) {
                                        reviewAgainstItemIds = new String[] { itemsToReviewAgainstStr };
                                    } else {
                                        reviewAgainstItemIds = itemsToReviewAgainstStr.split(";");
                                    }

                                    for (int i = 0; i < reviewAgainstItemIds.length; i++) {
                                        final String reviewAgainstItemId = reviewAgainstItemIds[i];
                                        //TODO: I believe this check is not correct, but I know of no other way to handle
                                        //this for now...
                                        //TODO: make this a batch query somehow
                                        if (new SQLQuery(conn, DbUtils.getConfiguration(mapId))
                                                .from(elementIdMappings)
                                                .where(elementIdMappings.mapId.eq(mapId)
                                                        .and(elementIdMappings.elementId.eq(reviewAgainstItemId)))
                                                .count() == 0) {
                                            log.warn(
                                                    "No element ID mapping exists for review against item with ID: "
                                                            + reviewAgainstItemId + " for map with ID: " + mapId
                                                            + ".  Skipping adding " + "review record...");
                                        } else {
                                            if (!reviewPairAlreadyParsed(reviewableItemId, reviewAgainstItemId)) {
                                                log.debug("Adding review item with reviewable item ID: "
                                                        + reviewableItemId + " and " + "review against item ID: "
                                                        + reviewAgainstItemId);
                                                reviewRecordsToInsert.add(createReviewItemRecord(reviewableItemId,
                                                        reviewScore, reviewAgainstItemId, mapId));
                                                reviewableItemIdToReviewAgainstItemIds.put(reviewableItemId,
                                                        reviewAgainstItemId);
                                                flushReviewRecords(reviewRecordsToInsert, maxRecordBatchSize,
                                                        logMsgStart);
                                                numReviewItemsAdded++;
                                            }
                                        }
                                    }
                                } else if (!tags.containsKey("hoot:review:uuid")
                                        || StringUtils.trimToNull(tags.get("hoot:review:uuid")) == null) {
                                    if (!reviewPairAlreadyParsed(reviewableItemId, reviewableItemId)) {
                                        log.debug("Adding review item with reviewable item ID: " + reviewableItemId
                                                + " and " + "review against item ID: " + reviewableItemId);
                                        reviewRecordsToInsert.add(createReviewItemRecord(reviewableItemId,
                                                reviewScore, reviewableItemId, mapId));
                                        reviewableItemIdToReviewAgainstItemIds.put(reviewableItemId,
                                                reviewableItemId);
                                        flushReviewRecords(reviewRecordsToInsert, maxRecordBatchSize, logMsgStart);
                                        numReviewItemsAdded++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        //final flush
        flushReviewRecords(reviewRecordsToInsert, 0, logMsgStart);

        log.debug("Wrote " + numReviewItemsAdded + " review items.");

        if (simulateFailure) {
            throw new Exception("Simulated test review data parse failure.");
        }

        return reviewRecordWritten;
    }
}