| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| = Ignite.C++ and Platform Interoperability |
| |
| == Overview |
| |
| When using Apache Ignite C++, it is quite common to have several C++ and Java nodes running in a single cluster. To seamlessly |
| interoperate between C++ and Java nodes, you need to take several aspects into consideration. Let's review them. |
| |
| == Binary Marshaller Configuration |
| |
| Ignite uses its binary marshaller for data, logic, messages serialization and deserialization. Due to architectural specificities, |
| Java and C++ nodes are started with different default settings of the binary marshaller that can lead to exceptions like |
| the one below during a node startup if you try to set up a heterogeneous cluster: |
| |
| [tabs] |
| -- |
| tab:Java[] |
| [source,java] |
| ---- |
| class org.apache.ignite.spi.IgniteSpiException: Local node's |
| binary configuration is not equal to remote node's binary configuration |
| [locNodeId=b3f0367d-3c2b-47b4-865f-a62c656b5d3f, |
| rmtNodeId=556a3f41-eab1-4d9f-b67c-d94d77ddd89d, |
| locBinaryCfg={globIdMapper=org.apache.ignite.binary.BinaryBasicIdMapper, |
| compactFooter=false, globSerializer=null}, rmtBinaryCfg=null] |
| ---- |
| -- |
| |
| To avoid the exception and to make sure Java and C++ nodes can co-exist in a single cluster, add the following binary |
| marshaller's settings to the Java configuration: |
| |
| [tabs] |
| -- |
| tab:XML[] |
| [source,xml] |
| ---- |
| <?xml version="1.0" encoding="UTF-8"?> |
| |
| <beans xmlns="http://www.springframework.org/schema/beans" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://www.springframework.org/schema/beans |
| http://www.springframework.org/schema/beans/spring-beans.xsd"> |
| |
| <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> |
| ... |
| <property name="binaryConfiguration"> |
| <bean class="org.apache.ignite.configuration.BinaryConfiguration"> |
| <property name="compactFooter" value="false"/> |
| |
| <property name="idMapper"> |
| <bean class="org.apache.ignite.binary.BinaryBasicIdMapper"> |
| <property name="lowerCase" value="true"/> |
| </bean> |
| </property> |
| </bean> |
| </property> |
| ... |
| </bean> |
| </beans> |
| ---- |
| -- |
| |
| == Basic Types Compatibility |
| |
| Your C++ application can put a value into the cluster and another Java application can read it back. The table below |
| shows how the types are matched between Java and C++: |
| |
| [opts="header"] |
| |=== |
| |Java Type | C++ Type |
| |
| | `boolean`, `java.lang.Boolean`| `bool` |
| | `byte`, `java.lang.Byte`| `int8_t` |
| | `short`, `java.lang.Short`| `int16_t` |
| | `int`, `java.lang.Integer`| `int32_t` |
| | `long`, `java.lang.Long`| `int64_t` |
| | `float`, `java.lang.Float`| `float` |
| | `double`, `java.lang.Double`| `double` |
| | `char`, `java.lang.Character`| `uint16_t` |
| | `java.lang.String`| `std::string`, `char[]` |
| | `java.util.Date`| `ignite::Date` |
| | `java.sql.Time`| `ignite::Time` |
| | `java.sql.Timestamp`| `ignite::Timestamp` |
| | `java.util.UUID`| `ignite::Guid` |
| |=== |
| |
| == Custom Types Compatibility |
| |
| To get access to the same application-specific object on both Java and C++ nodes, you need to describe it similarly in |
| both the languages. This includes the same type name, type id, fields id, hash code algorithm, as well as read/write functions |
| for the type. |
| |
| To do this on the C++ end, you need to use the `ignite::binary::BinaryType` class template. |
| |
| Let's consider the following example that defines a Java class that will be later read by a C++ application: |
| |
| [tabs] |
| -- |
| tab:Java[] |
| [source,java] |
| ---- |
| package org.apache.ignite.examples; |
| |
| public class CrossClass implements Binarylizable { |
| private long id; |
| |
| private int idPart; |
| |
| public void readBinary(BinaryReader reader) throws BinaryObjectException { |
| id = reader.readLong("id"); |
| idPart = reader.readInt("idPart"); |
| } |
| |
| public void writeBinary(BinaryWriter writer) throws BinaryObjectException { |
| writer.writeLong("id", id); |
| writer.writeInt("idPart", idPart); |
| } |
| } |
| ---- |
| -- |
| |
| Next, you create a counter-part on the C++ end: |
| |
| [tabs] |
| -- |
| tab:C++[] |
| [source,cpp] |
| ---- |
| namespace ignite |
| { |
| namespace binary |
| { |
| template<> |
| struct BinaryType<CrossClass> |
| { |
| static int32_t GetTypeId() |
| { |
| return GetBinaryStringHashCode("CrossClass"); |
| } |
| |
| static void GetTypeName(std::string& name) |
| { |
| name = "CrossClass"; |
| } |
| |
| static int32_t GetFieldId(const char* name) |
| { |
| return GetBinaryStringHashCode(name); |
| } |
| |
| static bool IsNull(const CrossClass& obj) |
| { |
| return false; |
| } |
| |
| static void GetNull(CrossClass& dst) |
| { |
| dst = CrossClass(); |
| } |
| |
| static void Read(BinaryReader& reader, CrossClass& dst) |
| { |
| dst.id = reader.ReadInt64("id"); |
| dst.idPart = reader.ReadInt32("idPart"); |
| } |
| |
| static void Write(BinaryWriter& writer, const CrossClass& obj) |
| { |
| writer.WriteInt64("id", obj.id); |
| writer.WriteInt32("idPart", obj.idPart); |
| } |
| }; |
| } |
| } |
| ---- |
| -- |
| |
| Finally, you need to use the following `BinaryConfiguration` for **both** Java and C++ nodes: |
| |
| [tabs] |
| -- |
| tab:XML[] |
| [source,xml] |
| ---- |
| <?xml version="1.0" encoding="UTF-8"?> |
| |
| <beans xmlns="http://www.springframework.org/schema/beans" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://www.springframework.org/schema/beans |
| http://www.springframework.org/schema/beans/spring-beans.xsd"> |
| |
| <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> |
| ... |
| <property name="binaryConfiguration"> |
| <bean class="org.apache.ignite.configuration.BinaryConfiguration"> |
| <property name="compactFooter" value="false"/> |
| |
| <property name="idMapper"> |
| <bean class="org.apache.ignite.binary.BinaryBasicIdMapper"> |
| <property name="lowerCase" value="true"/> |
| </bean> |
| </property> |
| |
| <property name="nameMapper"> |
| <bean class="org.apache.ignite.binary.BinaryBasicNameMapper"> |
| <property name="simpleName" value="true"/> |
| </bean> |
| </property> |
| |
| <property name="classNames"> |
| <list> |
| <value>org.apache.ignite.examples.CrossClass</value> |
| </list> |
| </property> |
| </bean> |
| </property> |
| ... |
| </bean> |
| </beans> |
| ---- |
| -- |
| |
| [CAUTION] |
| ==== |
| [discrete] |
| It is especially important to implement `GetTypeName()` and `GetTypeId()` methods in the right manner for the types that |
| are used for the keys. |
| ==== |
| |
| [CAUTION] |
| ==== |
| [discrete] |
| C++ function `GetBinaryStringHashCode()` always calculates hash as `BinaryBasicIdMapper` when its property `lowerCase` is set |
| to `true`. So make sure you have the correct configuration for the `BinaryBasicIdMapper` if you are going to use this |
| function to calculate the type id in C++. |
| ==== |
| |