Index: core/src/org/openstreetmap/josm/Main.java
===================================================================
--- core/src/org/openstreetmap/josm/Main.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/Main.java	(working copy)
@@ -587,7 +587,7 @@
         SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
         List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
         for (OsmDataLayer l: Main.map.mapView.getLayersOfType(OsmDataLayer.class)) {
-            if (l.requiresSaveToFile() || l.requiresUploadToServer()) {
+            if (l.requiresSaveToFile() || (l.isUploadAllowed() && l.requiresUploadToServer())) {
                 layersWithUnmodifiedChanges.add(l);
             }
         }
Index: core/src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- core/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/actions/UploadAction.java	(working copy)
@@ -21,6 +21,7 @@
 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.io.UploadDialog;
+import org.openstreetmap.josm.gui.io.UploadForbiddenException;
 import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -103,7 +104,16 @@
         return checkPreUploadConditions(layer, new APIDataSet(layer.data));
     }
 
-    protected void alertUnresolvedConflicts(OsmDataLayer layer) {
+    protected static void alertForbiddenUpload(OsmDataLayer layer) {
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                tr("Upload is forbidden for layer ''{0}''", layer.getName()),
+                tr("Warning"),
+                JOptionPane.INFORMATION_MESSAGE
+        );
+    }
+
+    protected static void alertUnresolvedConflicts(OsmDataLayer layer) {
         HelpAwareOptionPane.showOptionDialog(
                 Main.parent,
                 tr("<html>The data to be uploaded participates in unresolved conflicts of layer ''{0}''.<br>"
@@ -117,7 +127,7 @@
 
     /**
      * Check whether the preconditions are met to upload data in <code>apiData</code>.
-     * Makes sure primitives in <code>apiData</code> don't participate in conflicts and
+     * Makes sure upload is allowed, primitives in <code>apiData</code> don't participate in conflicts and
      * runs the installed {@see UploadHook}s.
      *
      * @param layer the source layer of the data to be uploaded
@@ -125,6 +135,10 @@
      * @return true, if the preconditions are met; false, otherwise
      */
     public boolean checkPreUploadConditions(OsmDataLayer layer, APIDataSet apiData) {
+        if (!layer.isUploadAllowed()) {
+            alertForbiddenUpload(layer);
+            return false;
+        }
         ConflictCollection conflicts = layer.getConflicts();
         if (apiData.participatesInConflict(conflicts)) {
             alertUnresolvedConflicts(layer);
@@ -177,14 +191,19 @@
             return;
         dialog.rememberUserInput();
 
-        Main.worker.execute(
-                new UploadPrimitivesTask(
-                        UploadDialog.getUploadDialog().getUploadStrategySpecification(),
-                        layer,
-                        apiData,
-                        UploadDialog.getUploadDialog().getChangeset()
-                )
-        );
+        try {
+            Main.worker.execute(
+                    new UploadPrimitivesTask(
+                            UploadDialog.getUploadDialog().getUploadStrategySpecification(),
+                            layer,
+                            apiData,
+                            UploadDialog.getUploadDialog().getChangeset()
+                    )
+            );
+        } catch (UploadForbiddenException ex) {
+            // This should not happen
+            System.err.println(ex.getMessage());
+        }
     }
 
     public void actionPerformed(ActionEvent e) {
Index: core/src/org/openstreetmap/josm/actions/UploadSelectionAction.java
===================================================================
--- core/src/org/openstreetmap/josm/actions/UploadSelectionAction.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/actions/UploadSelectionAction.java	(working copy)
@@ -25,6 +25,7 @@
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
 import org.openstreetmap.josm.gui.DefaultNameFormatter;
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.io.UploadForbiddenException;
 import org.openstreetmap.josm.gui.io.UploadSelectionDialog;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
@@ -87,6 +88,15 @@
     public void actionPerformed(ActionEvent e) {
         if (!isEnabled())
             return;
+        if (!getEditLayer().isUploadAllowed()) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("Upload is forbidden for layer ''{0}''", getEditLayer().getName()),
+                    tr("Warning"),
+                    JOptionPane.INFORMATION_MESSAGE
+            );
+            return;
+        }
         UploadHullBuilder builder = new UploadHullBuilder();
         UploadSelectionDialog dialog = new UploadSelectionDialog();
         Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(getEditLayer().data.getSelected());
@@ -117,7 +127,12 @@
             );
             return;
         }
-        uploadPrimitives(getEditLayer(), toUpload);
+        try {
+            uploadPrimitives(getEditLayer(), toUpload);
+        } catch (UploadForbiddenException ex) {
+            // This should not happen as it has been previously checked
+            System.err.println(ex.getMessage());
+        }
     }
 
     /**
@@ -144,8 +159,9 @@
      *
      * @param layer the data layer from which we upload a subset of primitives
      * @param toUpload the primitives to upload. If null or empty returns immediatelly
+     * @throws UploadForbiddenException if upload is not allowed for layer
      */
-    public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
+    public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) throws UploadForbiddenException {
         if (toUpload == null || toUpload.isEmpty()) return;
         UploadHullBuilder builder = new UploadHullBuilder();
         toUpload = builder.build(toUpload);
Index: core/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- core/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/data/osm/DataSet.java	(working copy)
@@ -124,6 +124,8 @@
     private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<AbstractDatasetChangedEvent>();
 
     private int highlightUpdateCount;
+    
+    private boolean uploadAllowed = true;
 
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
     private final Object selectionLock = new Object();
@@ -206,6 +208,14 @@
         this.version = version;
     }
 
+    public final boolean isUploadAllowed() {
+        return uploadAllowed;
+    }
+
+    public final void setUploadAllowed(boolean uploadAllowed) {
+        this.uploadAllowed = uploadAllowed;
+    }
+
     /*
      * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
      */
Index: core/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/MapView.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/gui/MapView.java	(working copy)
@@ -824,7 +824,7 @@
     }
 
     protected void refreshTitle() {
-        boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || editLayer.requiresUploadToServer());
+        boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || (editLayer.isUploadAllowed() && editLayer.requiresUploadToServer()));
         if (dirty) {
             JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor"));
         } else {
Index: core/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(working copy)
@@ -376,7 +376,7 @@
                     continue;
                 }
                 OsmDataLayer odl = (OsmDataLayer)l;
-                if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
+                if ((odl.requiresSaveToFile() || (odl.isUploadAllowed() && odl.requiresUploadToServer())) && odl.data.isModified()) {
                     layersWithUnmodifiedChanges.add(odl);
                 }
             }
Index: core/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(working copy)
@@ -427,32 +427,37 @@
                 }
                 dialog.rememberUserInput();
 
-                currentTask = new UploadLayerTask(
-                        UploadDialog.getUploadDialog().getUploadStrategySpecification(),
-                        layerInfo.getLayer(),
-                        monitor,
-                        UploadDialog.getUploadDialog().getChangeset()
-                );
-                currentFuture = worker.submit(currentTask);
                 try {
-                    // wait for the asynchronous task to complete
-                    //
-                    currentFuture.get();
-                } catch(CancellationException e) {
-                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
-                } catch(Exception e) {
-                    e.printStackTrace();
-                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
-                    ExceptionDialogUtil.explainException(e);
-                }
-                if (currentTask.isCanceled()) {
-                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
-                } else if (currentTask.isFailed()) {
-                    currentTask.getLastException().printStackTrace();
-                    ExceptionDialogUtil.explainException(currentTask.getLastException());
-                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
-                } else {
-                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.OK);
+                    currentTask = new UploadLayerTask(
+                            UploadDialog.getUploadDialog().getUploadStrategySpecification(),
+                            layerInfo.getLayer(),
+                            monitor,
+                            UploadDialog.getUploadDialog().getChangeset()
+                    );
+                    currentFuture = worker.submit(currentTask);
+                    try {
+                        // wait for the asynchronous task to complete
+                        //
+                        currentFuture.get();
+                    } catch(CancellationException e) {
+                        model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
+                    } catch(Exception e) {
+                        e.printStackTrace();
+                        model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                        ExceptionDialogUtil.explainException(e);
+                    }
+                    if (currentTask.isCanceled()) {
+                        model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
+                    } else if (currentTask.isFailed()) {
+                        currentTask.getLastException().printStackTrace();
+                        ExceptionDialogUtil.explainException(currentTask.getLastException());
+                        model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                    } else {
+                        model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.OK);
+                    }
+                } catch (UploadForbiddenException ex) {
+                    // This should not happen has it has been previously checked in checkPreUploadConditions()
+                    System.err.println(ex.getMessage());
                 }
                 currentTask = null;
                 currentFuture = null;
Index: core/src/org/openstreetmap/josm/gui/io/UploadForbiddenException.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/io/UploadForbiddenException.java	(revision 0)
+++ core/src/org/openstreetmap/josm/gui/io/UploadForbiddenException.java	(revision 0)
@@ -0,0 +1,13 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+public class UploadForbiddenException extends Exception {
+
+    public UploadForbiddenException(OsmDataLayer layer) {
+        super(tr("Upload is forbidden for layer ''{0}''", layer.getName()));
+    }
+}
Index: core/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(working copy)
@@ -53,12 +53,16 @@
      * @param layer the layer. Must not be null.
      * @param monitor  a progress monitor. If monitor is null, uses {@see NullProgressMonitor#INSTANCE}
      * @param changeset the changeset to be used
-     * @throws IllegalArgumentException thrown, if layer is null
+     * @throws UploadForbiddenException thrown if upload is not allowed for layer
+     * @throws IllegalArgumentException thrown if layer is null
      * @throws IllegalArgumentException thrown if strategy is null
      */
-    public UploadLayerTask(UploadStrategySpecification strategy, OsmDataLayer layer, ProgressMonitor monitor, Changeset changeset) {
+    public UploadLayerTask(UploadStrategySpecification strategy, OsmDataLayer layer, ProgressMonitor monitor, Changeset changeset) throws UploadForbiddenException {
         CheckParameterUtil.ensureParameterNotNull(layer, "layer");
         CheckParameterUtil.ensureParameterNotNull(strategy, "strategy");
+        if (!layer.isUploadAllowed()) {
+            throw new UploadForbiddenException(layer);
+        }
         if (monitor == null) {
             monitor = NullProgressMonitor.INSTANCE;
         }
Index: core/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java	(working copy)
@@ -55,16 +55,20 @@
      * @param toUpload the collection of primitives to upload. Set to the empty collection if null.
      * @param changeset the changeset to use for uploading. Must not be null. changeset.getId()
      * can be 0 in which case the upload task creates a new changeset
+     * @throws UploadForbiddenException thrown if upload is not allowed for layer
      * @throws IllegalArgumentException thrown if layer is null
      * @throws IllegalArgumentException thrown if toUpload is null
      * @throws IllegalArgumentException thrown if strategy is null
      * @throws IllegalArgumentException thrown if changeset is null
      */
-    public UploadPrimitivesTask(UploadStrategySpecification strategy, OsmDataLayer layer, APIDataSet toUpload, Changeset changeset) {
+    public UploadPrimitivesTask(UploadStrategySpecification strategy, OsmDataLayer layer, APIDataSet toUpload, Changeset changeset) throws UploadForbiddenException {
         super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);
         ensureParameterNotNull(layer,"layer");
         ensureParameterNotNull(strategy, "strategy");
         ensureParameterNotNull(changeset, "changeset");
+        if (!layer.isUploadAllowed()) {
+            throw new UploadForbiddenException(layer);
+        }
         this.toUpload = toUpload;
         this.layer = layer;
         this.changeset = changeset;
Index: core/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -394,7 +394,7 @@
 
 
     @Override public boolean isMergable(final Layer other) {
-        return other instanceof OsmDataLayer;
+        return other instanceof OsmDataLayer && (isUploadAllowed() == ((OsmDataLayer)other).isUploadAllowed());
     }
 
     @Override public void visitBoundingBox(final BoundingXYVisitor v) {
@@ -456,7 +456,8 @@
         p.add(new JLabel(nodeText, ImageProvider.get("data", "node"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
         p.add(new JLabel(wayText, ImageProvider.get("data", "way"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
         p.add(new JLabel(relationText, ImageProvider.get("data", "relation"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
-        p.add(new JLabel(tr("API version: {0}", (data.getVersion() != null) ? data.getVersion() : tr("unset"))));
+        p.add(new JLabel(tr("API version: {0}", (data.getVersion() != null) ? data.getVersion() : tr("unset"))), GBC.eop().insets(15,0,0,0));
+        p.add(new JLabel(tr("Upload allowed: {0}", isUploadAllowed() ? tr("yes") : tr("no"))), GBC.eop().insets(15,0,0,0));
 
         return p;
     }
@@ -701,4 +702,12 @@
          * change listener and already got notified.
          */
     }
+
+    public final boolean isUploadAllowed() {
+        return data.isUploadAllowed();
+    }
+
+    public final void setUploadAllowed(boolean uploadAllowed) {
+        data.setUploadAllowed(uploadAllowed);
+    }
 }
Index: core/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- core/src/org/openstreetmap/josm/io/OsmApi.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/io/OsmApi.java	(working copy)
@@ -260,9 +260,8 @@
     }
 
     /**
-     * Makes an XML string from an OSM primitive. Uses the OsmWriter class.
-     * @param o the OSM primitive
-     * @param addBody true to generate the full XML, false to only generate the encapsulating tag
+     * Makes an XML string from an OSM changeset. Uses the OsmWriter class.
+     * @param s the OSM changeset
      * @return XML string
      */
     private String toXml(Changeset s) {
Index: core/src/org/openstreetmap/josm/io/OsmExporter.java
===================================================================
--- core/src/org/openstreetmap/josm/io/OsmExporter.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/io/OsmExporter.java	(working copy)
@@ -73,10 +73,7 @@
             OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, layer.data.getVersion());
             layer.data.getReadLock().lock();
             try {
-                w.header();
-                w.writeDataSources(layer.data);
-                w.writeContent(layer.data);
-                w.footer();
+                w.writeLayer(layer);
                 w.close();
             } finally {
                 layer.data.getReadLock().unlock();
Index: core/src/org/openstreetmap/josm/io/OsmReader.java
===================================================================
--- core/src/org/openstreetmap/josm/io/OsmReader.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/io/OsmReader.java	(working copy)
@@ -116,6 +116,10 @@
             throwException(tr("Unsupported version: {0}", v));
         }
         ds.setVersion(v);
+        String upload = parser.getAttributeValue(null, "upload");
+        if (upload != null) {
+            ds.setUploadAllowed(Boolean.parseBoolean(upload));
+        }
         String generator = parser.getAttributeValue(null, "generator");
         Long uploadChangesetId = null;
         if (parser.getAttributeValue(null, "upload-changeset") != null) {
Index: core/src/org/openstreetmap/josm/io/OsmWriter.java
===================================================================
--- core/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/io/OsmWriter.java	(working copy)
@@ -25,6 +25,7 @@
 import org.openstreetmap.josm.data.osm.Tagged;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.tools.DateUtils;
 
 /**
@@ -53,19 +54,30 @@
     public void setWithBody(boolean wb) {
         this.withBody = wb;
     }
+    
     public void setChangeset(Changeset cs) {
         this.changeset = cs;
     }
+    
     public void setVersion(String v) {
         this.version = v;
     }
 
     public void header() {
+        header(null);
+    }
+    
+    public void header(Boolean upload) {
         out.println("<?xml version='1.0' encoding='UTF-8'?>");
         out.print("<osm version='");
         out.print(version);
+        if (upload != null) {
+            out.print("' upload='");
+            out.print(upload);
+        }
         out.println("' generator='JOSM'>");
     }
+    
     public void footer() {
         out.println("</osm>");
     }
@@ -82,6 +94,13 @@
         Collections.sort(result, byIdComparator);
         return result;
     }
+    
+    public void writeLayer(OsmDataLayer layer) {
+        header(layer.isUploadAllowed());
+        writeDataSources(layer.data);
+        writeContent(layer.data);
+        footer();
+    }
 
     public void writeContent(DataSet ds) {
         for (OsmPrimitive n : sortById(ds.getNodes())) {
Index: core/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java
===================================================================
--- core/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java	(revision 4917)
+++ core/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java	(working copy)
@@ -221,10 +221,7 @@
         OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, layer.data.getVersion());
         layer.data.getReadLock().lock();
         try {
-            w.header();
-            w.writeDataSources(layer.data);
-            w.writeContent(layer.data);
-            w.footer();
+            w.writeLayer(layer);
             w.flush();
         } finally {
             layer.data.getReadLock().unlock();
