Special Character Issue : MQ message PUT error : java.nio.charset.UnmappableCharacterException
Special Character Issue : MQ message PUT error : java.nio.charset.UnmappableCharacterException
我有一个设置,其中有一个 JMS 生产者和 JMS 接收器。发件人应用程序发送如下消息:
source text ⟨е, ё, и, ю, я⟩ abcdefg
JMS 接收方在收到消息后,使用纯 IBM MQ 将其放入 IBM MQ 队列 API 类.
将此消息发送到 MQ 时出现以下异常:
INFO | 2020-09-17 09:45:19 | [main] mimq.MQReceiver (MQReceiver.java:211) - IO Exception Occurred: Input length = 1
java.nio.charset.UnmappableCharacterException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:282)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:816)
at com.ibm.mq.jmqi.system.JmqiCodepage.stringToBytes(JmqiCodepage.java:923)
at com.ibm.mq.MQMessage.writeString(MQMessage.java:2848)
at com.ibm.mimq.MQReceiver.sendToAnotherQueue(MQReceiver.java:192)
at com.ibm.mimq.MQReceiver.main(MQReceiver.java:113)
下面是我的 MQ PUT 代码:
public static void sendToLocalQueue(String msg) {
int port = 1414;
String host = "some-host";
String channel = "some-channel";
String manager = "some-QM";
String user = "user";
String passwd = "passwd";
String qname = "TEST";
String qmname = "some-QM";
MQQueueManager qMgr;
MQQueue inputQ;
try {
Hashtable<String, String> h = new Hashtable<String, String>();
h.put(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_CLIENT);
MQEnvironment.properties = h;
MQEnvironment.hostname = host;
MQEnvironment.port = port;
MQEnvironment.channel = channel;
MQEnvironment.userID = user;
MQEnvironment.password = passwd;
MQEnvironment.disableTracing();
MQException.log = null;
qMgr = new MQQueueManager(manager);
MQMessage m = new MQMessage();
m.applicationOriginData = "AMPS";
m.messageType = MQC.MQMT_DATAGRAM;
m.format = MQC.MQFMT_STRING;
m.encoding = MQC.MQENC_NATIVE;
m.priority = 4;
m.persistence = MQC.MQPER_PERSISTENT;
m.characterSet = MQC.MQCCSI_Q_MGR;
//m.characterSet = 1208;
m.expiry = MQC.MQEI_UNLIMITED;
m.writeString(msg);
MQPutMessageOptions putOptions = new MQPutMessageOptions();
putOptions.options = MQC.MQPMO_SYNCPOINT | MQC.MQPMO_FAIL_IF_QUIESCING;
logger.info("Putting message to LAN MQ (TEST queue)....");
qMgr.put(qname, qmname, m, putOptions);
qMgr.commit();
} catch(MQException me) {
logger.info("Error Code : " +me.getErrorCode());
logger.info("LocalizedMessage : " +me.getLocalizedMessage());
logger.info("Message : " +me.getMessage());
logger.info("Reason : " +me.getReason());
me.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
logger.info("IO Exception Occurred : " +e.getLocalizedMessage());
e.printStackTrace();
}
}
由于不可映射字符,无法将消息放入队列。编码在队列管理器级别设置为 UTF-8。
但是当我替换下面的行时:m.characterSet = MQC.MQCCSI_Q_MGR;
随着行:m.characterSet = 1208;
问题不再存在。
我的问题是为什么没有在 MQ 级别完成此转换。我需要检查哪些设置以确保正确转换。我尝试了以下技术,但没有用:
Setting java parameter as : -Dfile.encoding=UTF-8
我的环境:
Server : Linux
MQ : 9.0 or 7.5
Java : 1.8
还有一件事要提,同样的设置在 7.5 上工作,但在迁移后不能在 MQ 9.0 上工作。我知道通过我上面的一行代码更改我可以传递消息。但是我想在 MQ 级别上了解,如果我遗漏了一些配置。任何建议将不胜感激。
谢谢。
更新
我将消息发送到 MQ 的客户端计算机具有 CCSID:MQMD.CodedCharSetId = 1208
我正在连接和发送消息的 MQ 服务器有这个:
getDefaultProperty(Object) returns [819(0x333)] Integer
setCCSID(int) setter [819(0x333)]
因此,当我在我的代码中明确设置 1208 时,它可以正常工作。否则转换失败。
UPDATE-2
我在罐子里看到的值 MQC.MQCCSI_Q_MGR is Zero
。因此代码是这样设计的,如果值为零,它将从设置为 819 的 Jar 中获取默认值。我在打开 MQ 跟踪时了解到这一点。代码是这样的:
getDefaultProperty(Object) returns [819(0x333)] Integer
setCCSID(int) setter [819(0x333)]
此代码存在于 jar 中。所以我们需要在消息上显式设置字符集值。在我的例子中是 1208.
当本地连接到队列管理器(即使用 TRANSPORT_MQSERIES_BINDINGS
)时,代码:-
m.characterSet = MQC.MQCCSI_Q_MGR;
表示“取队列管理器中设置的CCSID 属性 CCSID”。您可以使用以下 MQSC 命令查看此 属性:-
DISPLAY QMGR CCSID
当作为客户端连接时(如您的代码所示),则代码:-
m.characterSet = MQC.MQCCSI_Q_MGR;
表示“从客户端计算机区域设置中查找 CCSID”。
IBM Knowledge Center 状态:-
For client applications, MQCCSI_Q_MGR
is filled in, based on the locale of the client rather than the one on the queue manager.
如果更改代码行以将消息 CCSID 显式设置为 1208 可以解决问题,那么您的客户端计算机区域设置似乎未设置为 UTF-8。
您可以通过浏览队列中的消息(不转换它)并查看 MQMD.CodedCharSetId
字段中的内容来查看客户端设置的内容。
这在 MQ v7.5 而不是 MQ v9.0 上起作用的原因是因为在 IBM MQ v8.0 之前,IBM MQ 类 用于 Java 编码数据使用 java.nio.charset.Charset.encode(CharBuffer)
导致默认替换格式错误或不可翻译的数据。由于默认 characterSet
是 819 (ASCII),这将导致您发送的任何无法转换为 ASCII 的字符被透明地替换为默认替换字符,在大多数情况下,这意味着数据被替换为?
个字符。
在 v8.0 之后,默认行为更改为将这种情况报告为错误,默认情况下不再替换格式错误或不可翻译的数据。
您将字符集设置为 UTF-8 的解决方案是最佳解决方案,因为这会导致发送您打算发送的数据。
另一种选择是告诉 MQ 使用先前的行为。
IBM MQ 9.0 知识中心页面 Developing applications>Developing JMS and Java applications>Using IBM MQ classes for Java>Character string conversions in IBM MQ classes for Java 中描述了新行为的描述以及如何为 Java 配置 IBM MQ 类 以使用先前的行为:
From IBM® MQ Version 8.0, some of the default behavior regarding
character string conversion in the IBM MQ classes for Java™ has
changed.
Before IBM MQ Version 8.0, string conversions in IBM MQ classes for
Java was done by calling the
java.nio.charset.Charset.decode(ByteBuffer) and
Charset.encode(CharBuffer) methods.
Using either of these methods results in a default replacement (
REPLACE) of malformed or untranslatable data.
This behavior can obscure errors in applications, and lead to
unexpected characters, for example ?, in translated data. From IBM MQ
Version 8.0, to detect such issues earlier and more effectively, the
IBM MQ classes for Java use CharsetEncoders and CharsetDecoders
directly and configure the handling of malformed and untranslatable
data explicitly.
From IBM MQ Version 8.0, the default behavior is to REPORT such issues
by throwing a suitable MQException.
...
Setting system defaults
--
From IBM MQ Version 8.0, the following two Java system properties are
available to configure default behavior regarding character string
conversion.
com.ibm.mq.cfg.jmqi.UnmappableCharacterAction Specifies the action to be taken for untranslatable data on encoding and decoding.
The value can be REPORT, REPLACE, or IGNORE.
com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement Sets or gets the replacement bytes to apply when a character cannot be mapped
in an encoding operation The default Java replacement string is used
in decoding operations.
To avoid confusion between Java character and native byte
representations, you should specify
com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement as a decimal number
representing the replacement byte in the native character set.
For example, the decimal value of ?, as a native byte, is 63 if the
native character set is ASCII-based, such as ISO-8859-1, while it is
111 if the native character set is EBCDIC.
如果您想模仿之前的行为,您可以设置以下系统属性:
-Dcom.ibm.mq.cfg.jmqi.UnmappableCharacterAction=REPLACE
-Dcom.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement=63
您也可以使用如下方式以编程方式设置它:
System.setProperty("com.ibm.mq.cfg.jmqi.UnmappableCharacterAction", "REPLACE");
System.setProperty("com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement", "63");
我有一个设置,其中有一个 JMS 生产者和 JMS 接收器。发件人应用程序发送如下消息:
source text ⟨е, ё, и, ю, я⟩ abcdefg
JMS 接收方在收到消息后,使用纯 IBM MQ 将其放入 IBM MQ 队列 API 类.
将此消息发送到 MQ 时出现以下异常:
INFO | 2020-09-17 09:45:19 | [main] mimq.MQReceiver (MQReceiver.java:211) - IO Exception Occurred: Input length = 1
java.nio.charset.UnmappableCharacterException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:282)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:816)
at com.ibm.mq.jmqi.system.JmqiCodepage.stringToBytes(JmqiCodepage.java:923)
at com.ibm.mq.MQMessage.writeString(MQMessage.java:2848)
at com.ibm.mimq.MQReceiver.sendToAnotherQueue(MQReceiver.java:192)
at com.ibm.mimq.MQReceiver.main(MQReceiver.java:113)
下面是我的 MQ PUT 代码:
public static void sendToLocalQueue(String msg) {
int port = 1414;
String host = "some-host";
String channel = "some-channel";
String manager = "some-QM";
String user = "user";
String passwd = "passwd";
String qname = "TEST";
String qmname = "some-QM";
MQQueueManager qMgr;
MQQueue inputQ;
try {
Hashtable<String, String> h = new Hashtable<String, String>();
h.put(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_CLIENT);
MQEnvironment.properties = h;
MQEnvironment.hostname = host;
MQEnvironment.port = port;
MQEnvironment.channel = channel;
MQEnvironment.userID = user;
MQEnvironment.password = passwd;
MQEnvironment.disableTracing();
MQException.log = null;
qMgr = new MQQueueManager(manager);
MQMessage m = new MQMessage();
m.applicationOriginData = "AMPS";
m.messageType = MQC.MQMT_DATAGRAM;
m.format = MQC.MQFMT_STRING;
m.encoding = MQC.MQENC_NATIVE;
m.priority = 4;
m.persistence = MQC.MQPER_PERSISTENT;
m.characterSet = MQC.MQCCSI_Q_MGR;
//m.characterSet = 1208;
m.expiry = MQC.MQEI_UNLIMITED;
m.writeString(msg);
MQPutMessageOptions putOptions = new MQPutMessageOptions();
putOptions.options = MQC.MQPMO_SYNCPOINT | MQC.MQPMO_FAIL_IF_QUIESCING;
logger.info("Putting message to LAN MQ (TEST queue)....");
qMgr.put(qname, qmname, m, putOptions);
qMgr.commit();
} catch(MQException me) {
logger.info("Error Code : " +me.getErrorCode());
logger.info("LocalizedMessage : " +me.getLocalizedMessage());
logger.info("Message : " +me.getMessage());
logger.info("Reason : " +me.getReason());
me.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
logger.info("IO Exception Occurred : " +e.getLocalizedMessage());
e.printStackTrace();
}
}
由于不可映射字符,无法将消息放入队列。编码在队列管理器级别设置为 UTF-8。
但是当我替换下面的行时:m.characterSet = MQC.MQCCSI_Q_MGR;
随着行:m.characterSet = 1208;
问题不再存在。
我的问题是为什么没有在 MQ 级别完成此转换。我需要检查哪些设置以确保正确转换。我尝试了以下技术,但没有用:
Setting java parameter as : -Dfile.encoding=UTF-8
我的环境:
Server : Linux
MQ : 9.0 or 7.5
Java : 1.8
还有一件事要提,同样的设置在 7.5 上工作,但在迁移后不能在 MQ 9.0 上工作。我知道通过我上面的一行代码更改我可以传递消息。但是我想在 MQ 级别上了解,如果我遗漏了一些配置。任何建议将不胜感激。
谢谢。
更新
我将消息发送到 MQ 的客户端计算机具有 CCSID:MQMD.CodedCharSetId = 1208
我正在连接和发送消息的 MQ 服务器有这个:
getDefaultProperty(Object) returns [819(0x333)] Integer
setCCSID(int) setter [819(0x333)]
因此,当我在我的代码中明确设置 1208 时,它可以正常工作。否则转换失败。
UPDATE-2
我在罐子里看到的值 MQC.MQCCSI_Q_MGR is Zero
。因此代码是这样设计的,如果值为零,它将从设置为 819 的 Jar 中获取默认值。我在打开 MQ 跟踪时了解到这一点。代码是这样的:
getDefaultProperty(Object) returns [819(0x333)] Integer
setCCSID(int) setter [819(0x333)]
此代码存在于 jar 中。所以我们需要在消息上显式设置字符集值。在我的例子中是 1208.
当本地连接到队列管理器(即使用 TRANSPORT_MQSERIES_BINDINGS
)时,代码:-
m.characterSet = MQC.MQCCSI_Q_MGR;
表示“取队列管理器中设置的CCSID 属性 CCSID”。您可以使用以下 MQSC 命令查看此 属性:-
DISPLAY QMGR CCSID
当作为客户端连接时(如您的代码所示),则代码:-
m.characterSet = MQC.MQCCSI_Q_MGR;
表示“从客户端计算机区域设置中查找 CCSID”。
IBM Knowledge Center 状态:-
For client applications,
MQCCSI_Q_MGR
is filled in, based on the locale of the client rather than the one on the queue manager.
如果更改代码行以将消息 CCSID 显式设置为 1208 可以解决问题,那么您的客户端计算机区域设置似乎未设置为 UTF-8。
您可以通过浏览队列中的消息(不转换它)并查看 MQMD.CodedCharSetId
字段中的内容来查看客户端设置的内容。
这在 MQ v7.5 而不是 MQ v9.0 上起作用的原因是因为在 IBM MQ v8.0 之前,IBM MQ 类 用于 Java 编码数据使用 java.nio.charset.Charset.encode(CharBuffer)
导致默认替换格式错误或不可翻译的数据。由于默认 characterSet
是 819 (ASCII),这将导致您发送的任何无法转换为 ASCII 的字符被透明地替换为默认替换字符,在大多数情况下,这意味着数据被替换为?
个字符。
在 v8.0 之后,默认行为更改为将这种情况报告为错误,默认情况下不再替换格式错误或不可翻译的数据。
您将字符集设置为 UTF-8 的解决方案是最佳解决方案,因为这会导致发送您打算发送的数据。
另一种选择是告诉 MQ 使用先前的行为。
IBM MQ 9.0 知识中心页面 Developing applications>Developing JMS and Java applications>Using IBM MQ classes for Java>Character string conversions in IBM MQ classes for Java 中描述了新行为的描述以及如何为 Java 配置 IBM MQ 类 以使用先前的行为:
From IBM® MQ Version 8.0, some of the default behavior regarding character string conversion in the IBM MQ classes for Java™ has changed.
Before IBM MQ Version 8.0, string conversions in IBM MQ classes for Java was done by calling the java.nio.charset.Charset.decode(ByteBuffer) and Charset.encode(CharBuffer) methods.
Using either of these methods results in a default replacement ( REPLACE) of malformed or untranslatable data.
This behavior can obscure errors in applications, and lead to unexpected characters, for example ?, in translated data. From IBM MQ Version 8.0, to detect such issues earlier and more effectively, the IBM MQ classes for Java use CharsetEncoders and CharsetDecoders directly and configure the handling of malformed and untranslatable data explicitly.
From IBM MQ Version 8.0, the default behavior is to REPORT such issues by throwing a suitable MQException.
...
Setting system defaults --
From IBM MQ Version 8.0, the following two Java system properties are available to configure default behavior regarding character string conversion.
com.ibm.mq.cfg.jmqi.UnmappableCharacterAction Specifies the action to be taken for untranslatable data on encoding and decoding. The value can be REPORT, REPLACE, or IGNORE.
com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement Sets or gets the replacement bytes to apply when a character cannot be mapped in an encoding operation The default Java replacement string is used in decoding operations.
To avoid confusion between Java character and native byte representations, you should specify com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement as a decimal number representing the replacement byte in the native character set.
For example, the decimal value of ?, as a native byte, is 63 if the native character set is ASCII-based, such as ISO-8859-1, while it is 111 if the native character set is EBCDIC.
如果您想模仿之前的行为,您可以设置以下系统属性:
-Dcom.ibm.mq.cfg.jmqi.UnmappableCharacterAction=REPLACE
-Dcom.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement=63
您也可以使用如下方式以编程方式设置它:
System.setProperty("com.ibm.mq.cfg.jmqi.UnmappableCharacterAction", "REPLACE");
System.setProperty("com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement", "63");