// License: GPL. For details, see LICENSE file. package org.openstreetmap.hot.sds; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import org.openstreetmap.josm.actions.upload.UploadHook; import org.openstreetmap.josm.data.APIDataSet; import org.openstreetmap.josm.data.osm.INode; import org.openstreetmap.josm.data.osm.IPrimitive; import org.openstreetmap.josm.data.osm.IRelation; import org.openstreetmap.josm.data.osm.IWay; import org.openstreetmap.josm.data.osm.OsmPrimitive; /** * This upload hook does the following things: * * 1. Find out if there are any changes in the special tags that need to * be uploaded to a different server. * 2. Find out if any objects that did have special tags have now been * deleted, resulting in tag deletions on the special server. * 3. Find out if any objects carrying special tags have been newly created. * 4. Also, if it is determined that an object modification consists exclusively * of special tags, then skip uploading that object, by removing it from * the apiDataSet. * * This upload hook stores its findings with the SeparateDataStorePlugin, and * changes are sent to the SDS server only after the OSM upload has sucessfully * completed. The UploadSuccessHook is responsible for that. */ public class DetermineSdsModificationsUploadHook implements UploadHook { private SeparateDataStorePlugin plugin; DetermineSdsModificationsUploadHook(SeparateDataStorePlugin plugin) { this.plugin = plugin; } @Override public boolean checkUpload(APIDataSet apiDataSet) { ArrayList<OsmPrimitive> droplist = new ArrayList<>(); // check deleted primitives for special tags. for (OsmPrimitive del : apiDataSet.getPrimitivesToDelete()) { IPrimitive old = plugin.getOriginalPrimitive(del); if (hasSpecialTags(old)) { // request deletion of all tags for this object on special server. plugin.enqueueForUpload(del, new HashMap<String, String>(), false); } } // check modified primitives. for (OsmPrimitive upd : apiDataSet.getPrimitivesToUpdate()) { HashSet<String> allKeys = new HashSet<>(); boolean specialTags = false; // process tags of new object for (String key : upd.keySet()) { allKeys.add(key); if (!specialTags && isSpecialKey(key)) specialTags = true; } // process tags of old object IPrimitive old = plugin.getOriginalPrimitive(upd); if (old != null) { for (String key : old.keySet()) { allKeys.add(key); if (!specialTags && isSpecialKey(key)) specialTags = true; } } // if neither has special tags, done with this object. if (!specialTags) continue; // special tags are involved. find out what, exactly, has changed. boolean changeInSpecialTags = false; boolean changeInOtherTags = false; for (String key : allKeys) { if (old.get(key) == null || upd.get(key) == null || !old.get(key).equals(upd.get(key))) { if (isSpecialKey(key)) changeInSpecialTags = true; else changeInOtherTags = true; if (changeInSpecialTags && changeInOtherTags) break; } } // change *only* in standard tags - done with this object. if (!changeInSpecialTags) continue; // assemble new set of special tags. might turn out to be empty. HashMap<String, String> newSpecialTags = new HashMap<>(); for (String key : upd.keySet()) { if (isSpecialKey(key)) newSpecialTags.put(key, upd.get(key)); } boolean uploadToOsm = changeInOtherTags; // not done yet: if no changes in standard tags, we need to find out if // there were changes in the other properties (node: lat/lon, way/relation: // member list). If the answer is no, then the object must be removed from // JOSM's normal upload queue, else we would be uploading a non-edit. if (!changeInOtherTags) { switch(old.getType()) { case NODE: INode nold = (INode) old; INode nupd = (INode) upd; uploadToOsm = !(nold.getCoor().equals(nupd.getCoor())); break; case WAY: IWay wold = (IWay) old; IWay wupd = (IWay) upd; if (wold.getNodesCount() != wupd.getNodesCount()) { uploadToOsm = true; break; } for (int i = 0; i < wold.getNodesCount(); i++) { if (wold.getNodeId(i) != wupd.getNodeId(i)) { uploadToOsm = true; break; } } break; case RELATION: IRelation rold = (IRelation) old; IRelation rupd = (IRelation) upd; if (rold.getMembersCount() != rupd.getMembersCount()) { uploadToOsm = true; break; } for (int i = 0; i < rold.getMembersCount(); i++) { if (rold.getMemberType(i) != rupd.getMemberType(i) || rold.getMemberId(i) != rupd.getMemberId(i)) { uploadToOsm = true; break; } } break; default: throw new AssertionError("unexpected case: " + old.getType()); } } // request that new set of special tags be uploaded plugin.enqueueForUpload(upd, newSpecialTags, !uploadToOsm); // we cannot remove from getPrimitivesToUpdate, this would result in a // ConcurrentModificationException. if (!uploadToOsm) droplist.add(upd); } apiDataSet.getPrimitivesToUpdate().removeAll(droplist); // check added primitives. for (OsmPrimitive add : apiDataSet.getPrimitivesToAdd()) { // assemble new set of special tags. might turn out to be empty. HashMap<String, String> newSpecialTags = new HashMap<>(); for (String key : add.keySet()) { if (isSpecialKey(key)) newSpecialTags.put(key, add.get(key)); } if (!newSpecialTags.isEmpty()) plugin.enqueueForUpload(add, newSpecialTags, false); } // FIXME it is possible that the list of OSM edits is totally empty. return true; } boolean hasSpecialTags(IPrimitive p) { for (String key : p.keySet()) { if (isSpecialKey(key)) return true; } return false; } boolean isSpecialKey(String key) { return key.startsWith(plugin.getIgnorePrefix()); } }