Merge remote-tracking branch 'remotes/community/ignite-1.6.11' into ignite-4154-opt2
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index a75027b..22cb9a6 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -478,6 +478,15 @@
     public static final String IGNITE_BINARY_DONT_WRAP_TREE_STRUCTURES = "IGNITE_BINARY_DONT_WRAP_TREE_STRUCTURES";
 
     /**
+     * When set to {@code true} fields are written by BinaryMarshaller in sorted order. Otherwise
+     * the natural order is used.
+     * <p>
+     * @deprecated Should be removed in Apache Ignite 2.0.
+     */
+    @Deprecated
+    public static final String IGNITE_BINARY_SORT_OBJECT_FIELDS = "IGNITE_BINARY_SORT_OBJECT_FIELDS";
+
+    /**
      * Enforces singleton.
      */
     private IgniteSystemProperties() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
index 4c824d4..d05ce71 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
@@ -25,14 +25,15 @@
 import java.lang.reflect.Proxy;
 import java.math.BigDecimal;
 import java.sql.Timestamp;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.UUID;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.binary.BinaryObjectException;
@@ -120,6 +121,9 @@
     /** */
     private final Class<?>[] intfs;
 
+    /** Whether stable schema was published. */
+    private volatile boolean stableSchemaPublished;
+
     /**
      * @param ctx Context.
      * @param cls Class.
@@ -269,10 +273,14 @@
             case OBJECT:
                 // Must not use constructor to honor transient fields semantics.
                 ctor = null;
-                ArrayList<BinaryFieldAccessor> fields0 = new ArrayList<>();
                 stableFieldsMeta = metaDataEnabled ? new HashMap<String, Integer>() : null;
 
-                BinarySchema.Builder schemaBuilder = BinarySchema.Builder.newBuilder();
+                Map<Object, BinaryFieldAccessor> fields0;
+
+                if (BinaryUtils.FIELDS_SORTED_ORDER)
+                    fields0 = new TreeMap<>();
+                else
+                    fields0 = new LinkedHashMap<>();
 
                 Set<String> duplicates = duplicateFields(cls);
 
@@ -300,9 +308,7 @@
 
                             BinaryFieldAccessor fieldInfo = BinaryFieldAccessor.create(f, fieldId);
 
-                            fields0.add(fieldInfo);
-
-                            schemaBuilder.addField(fieldId);
+                            fields0.put(name, fieldInfo);
 
                             if (metaDataEnabled)
                                 stableFieldsMeta.put(name, fieldInfo.mode().typeId());
@@ -310,7 +316,12 @@
                     }
                 }
 
-                fields = fields0.toArray(new BinaryFieldAccessor[fields0.size()]);
+                fields = fields0.values().toArray(new BinaryFieldAccessor[fields0.size()]);
+
+                BinarySchema.Builder schemaBuilder = BinarySchema.Builder.newBuilder();
+
+                for (BinaryFieldAccessor field : fields)
+                    schemaBuilder.addField(field.id);
 
                 stableSchema = schemaBuilder.build();
 
@@ -747,6 +758,18 @@
                 break;
 
             case OBJECT:
+                if (userType && !stableSchemaPublished) {
+                    // Update meta before write object with new schema
+                    BinaryMetadata meta = new BinaryMetadata(typeId, typeName, stableFieldsMeta,
+                        affKeyFieldName, Collections.singleton(stableSchema), false);
+
+                    ctx.updateMetadata(typeId, meta);
+
+                    schemaReg.addSchema(stableSchema.schemaId(), stableSchema);
+
+                    stableSchemaPublished = true;
+                }
+
                 if (preWrite(writer, obj)) {
                     try {
                         for (BinaryFieldAccessor info : fields)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index 2ac8b7f..a72e7ac 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -785,12 +785,9 @@
             registered
         );
 
-        if (!deserialize) {
-            Collection<BinarySchema> schemas = desc.schema() != null ? Collections.singleton(desc.schema()) : null;
-
+        if (!deserialize)
             metaHnd.addMeta(typeId,
-                new BinaryMetadata(typeId, typeName, desc.fieldsMeta(), affFieldName, schemas, desc.isEnum()).wrap(this));
-        }
+                new BinaryMetadata(typeId, typeName, desc.fieldsMeta(), affFieldName, null, desc.isEnum()).wrap(this));
 
         descByCls.put(cls, desc);
 
@@ -1123,7 +1120,6 @@
         cls2Mappers.put(clsName, mapper);
 
         Map<String, Integer> fieldsMeta = null;
-        Collection<BinarySchema> schemas = null;
 
         if (cls != null) {
             if (serializer == null) {
@@ -1148,7 +1144,6 @@
             );
 
             fieldsMeta = desc.fieldsMeta();
-            schemas = desc.schema() != null ? Collections.singleton(desc.schema()) : null;
 
             descByCls.put(cls, desc);
 
@@ -1157,7 +1152,7 @@
             predefinedTypes.put(id, desc);
         }
 
-        metaHnd.addMeta(id, new BinaryMetadata(id, typeName, fieldsMeta, affKeyFieldName, schemas, isEnum).wrap(this));
+        metaHnd.addMeta(id, new BinaryMetadata(id, typeName, fieldsMeta, affKeyFieldName, null, isEnum).wrap(this));
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index 25d87ff..b304082 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -128,6 +128,10 @@
     public static final boolean WRAP_TREES =
         !IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_BINARY_DONT_WRAP_TREE_STRUCTURES);
 
+    /** Whether to sort field in binary objects (doesn't affect Binarylizable). */
+    public static final boolean FIELDS_SORTED_ORDER =
+        IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_BINARY_SORT_OBJECT_FIELDS);
+
     /** Field type names. */
     private static final String[] FIELD_TYPE_NAMES;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
index 2c761925..f0bc874 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
@@ -42,6 +42,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 /**
  *
@@ -522,7 +523,10 @@
         Object val = val0 == null ? new BinaryValueWithType(BinaryUtils.typeByClass(Object.class), null) : val0;
 
         if (assignedVals == null)
-            assignedVals = new LinkedHashMap<>();
+            if (BinaryUtils.FIELDS_SORTED_ORDER)
+                assignedVals = new TreeMap<>();
+            else
+                assignedVals = new LinkedHashMap<>();
 
         Object oldVal = assignedVals.put(name, val);
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTestTopologyChange.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTestTopologyChange.cs
index c1f2c53..9e80c08 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTestTopologyChange.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Dataload/DataStreamerTestTopologyChange.cs
@@ -19,7 +19,6 @@
 {
     using System;
     using System.Threading;
-    using System.Threading.Tasks;
     using NUnit.Framework;
 
     /// <summary>
@@ -59,7 +58,13 @@
                 var task = streamer.AddData(2, 3);
                 streamer.Flush();
 
-                AssertThrowsCacheStopped(task);
+                var ex = Assert.Throws<AggregateException>(task.Wait).InnerException;
+
+                Assert.IsNotNull(ex);
+
+                Assert.AreEqual("Java exception occurred [class=org.apache.ignite.cache." +
+                                "CacheServerNotFoundException, message=Failed to find server node for cache " +
+                                "(all affinity nodes have left the grid or cache was stopped): cache]", ex.Message);
             }
         }
 
@@ -86,19 +91,13 @@
                 task = streamer.AddData(2, 3);
                 streamer.Flush();
 
-                AssertThrowsCacheStopped(task);
-            }
-        }
+                var ex = Assert.Throws<AggregateException>(task.Wait).InnerException;
 
-        /// <summary>
-        /// Asserts that cache stopped error is thrown.
-        /// </summary>
-        private static void AssertThrowsCacheStopped(Task task)
-        {
-            var ex = Assert.Throws<AggregateException>(task.Wait);
-            Assert.IsTrue(ex.InnerException.Message.Contains(
-                "Failed to find server node for cache " +
-                "(all affinity nodes have left the grid or cache was stopped):"));
+                Assert.IsNotNull(ex);
+
+                Assert.AreEqual("class org.apache.ignite.IgniteCheckedException: DataStreamer data loading failed.", 
+                    ex.Message);
+            }
         }
     }
 }
diff --git a/modules/spring/src/test/java/org/apache/ignite/spring/IgniteStartFromStreamConfigurationTest.java b/modules/spring/src/test/java/org/apache/ignite/spring/IgniteStartFromStreamConfigurationTest.java
index 0ef08f1..421011f 100644
--- a/modules/spring/src/test/java/org/apache/ignite/spring/IgniteStartFromStreamConfigurationTest.java
+++ b/modules/spring/src/test/java/org/apache/ignite/spring/IgniteStartFromStreamConfigurationTest.java
@@ -30,21 +30,25 @@
  * Checks starts from Stream.
  */
 public class IgniteStartFromStreamConfigurationTest extends GridCommonAbstractTest {
-    /** Tests starts from Stream. */
+    /**
+     * Tests starts from stream.
+     *
+     * @throws Exception If failed.
+     */
     public void testStartFromStream() throws Exception {
         String cfg = "examples/config/example-cache.xml";
 
         URL cfgLocation = U.resolveIgniteUrl(cfg);
 
-        Ignite grid = Ignition.start(new FileInputStream(cfgLocation.getFile()));
+        try (Ignite grid = Ignition.start(new FileInputStream(cfgLocation.getFile()))) {
+            grid.cache(null).put("1", "1");
 
-        grid.cache(null).put("1", "1");
+            assert grid.cache(null).get("1").equals("1");
 
-        assert grid.cache(null).get("1").equals("1");
+            IgniteConfiguration icfg = Ignition.loadSpringBean(new FileInputStream(cfgLocation.getFile()), "ignite.cfg");
 
-        IgniteConfiguration icfg = Ignition.loadSpringBean(new FileInputStream(cfgLocation.getFile()), "ignite.cfg");
-
-        assert icfg.getCacheConfiguration()[0].getAtomicityMode() == CacheAtomicityMode.ATOMIC;
+            assert icfg.getCacheConfiguration()[0].getAtomicityMode() == CacheAtomicityMode.ATOMIC;
+        }
     }
 
 }
\ No newline at end of file