/** * 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. */ package org.apache.activemq.bugs; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Random; import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.command.ActiveMQBytesMessage; import org.apache.activemq.command.ActiveMQMessage; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; public class AMQ5381Test { public static final byte[] ORIG_MSG_CONTENT = randomByteArray(); public static final String AMQ5381_EXCEPTION_MESSAGE = "java.util.zip.DataFormatException: incorrect header check"; private BrokerService brokerService; private String brokerURI; @Rule public TestName name = new TestName(); @Before public void startBroker() throws Exception { brokerService = new BrokerService(); brokerService.setPersistent(false); brokerService.setUseJmx(false); brokerService.addConnector("tcp://localhost:0"); brokerService.start(); brokerService.waitUntilStarted(); brokerURI = brokerService.getTransportConnectorByScheme("tcp").getPublishableConnectString(); } @After public void stopBroker() throws Exception { if (brokerService != null) { brokerService.stop(); } } private ActiveMQConnection createConnection(boolean useCompression) throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURI); factory.setUseCompression(useCompression); Connection connection = factory.createConnection(); connection.start(); return (ActiveMQConnection) connection; } @Test public void amq5381Test() throws Exception { // Consumer Configured for (useCompression=true) final ActiveMQConnection consumerConnection = createConnection(true); final Session consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); final Queue consumerQueue = consumerSession.createQueue(name.getMethodName()); final MessageConsumer consumer = consumerSession.createConsumer(consumerQueue); // Producer Configured for (useCompression=false) final ActiveMQConnection producerConnection = createConnection(false); final Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); final Queue producerQueue = producerSession.createQueue(name.getMethodName()); try { final ActiveMQBytesMessage messageProduced = (ActiveMQBytesMessage) producerSession.createBytesMessage(); messageProduced.writeBytes(ORIG_MSG_CONTENT); Assert.assertFalse(messageProduced.isReadOnlyBody()); Assert.assertFalse( "Produced Message's 'compressed' flag should remain false until the message is sent (where it will be compressed, if necessary)", messageProduced.isCompressed()); final MessageProducer producer = producerSession.createProducer(null); producer.send(producerQueue, messageProduced); Assert.assertEquals("Once sent, the produced Message's 'compressed' flag should match its Connection's 'useCompression' flag", producerConnection.isUseCompression(), messageProduced.isCompressed()); final ActiveMQBytesMessage messageConsumed = (ActiveMQBytesMessage) consumer.receive(); Assert.assertNotNull(messageConsumed); Assert.assertTrue("Consumed Message should be read-only", messageConsumed.isReadOnlyBody()); Assert.assertEquals("Consumed Message's 'compressed' flag should match the produced Message's 'compressed' flag", messageProduced.isCompressed(), messageConsumed.isCompressed()); // ensure consumed message content matches what was originally set final byte[] consumedMsgContent = new byte[(int) messageConsumed.getBodyLength()]; messageConsumed.readBytes(consumedMsgContent); Assert.assertTrue("Consumed Message content should match the original Message content", Arrays.equals(ORIG_MSG_CONTENT, consumedMsgContent)); // make message writable so the consumer can modify and reuse it makeWritable(messageConsumed); // modify message, attempt to trigger DataFormatException due // to old incorrect compression logic try { messageConsumed.setStringProperty(this.getClass().getName(), "test"); } catch (JMSException jmsE) { if (AMQ5381_EXCEPTION_MESSAGE.equals(jmsE.getMessage())) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); jmsE.printStackTrace(pw); Assert.fail("AMQ5381 Error State Achieved: attempted to decompress BytesMessage contents that are not compressed\n" + sw.toString()); } else { throw jmsE; } } Assert.assertEquals( "The consumed Message's 'compressed' flag should still match the produced Message's 'compressed' flag after it has been made writable", messageProduced.isCompressed(), messageConsumed.isCompressed()); // simulate re-publishing message simulatePublish(messageConsumed); // ensure consumed message content matches what was originally set final byte[] modifiedMsgContent = new byte[(int) messageConsumed.getBodyLength()]; messageConsumed.readBytes(modifiedMsgContent); Assert.assertTrue( "After the message properties are modified and it is re-published, its message content should still match the original message content", Arrays.equals(ORIG_MSG_CONTENT, modifiedMsgContent)); } finally { producerSession.close(); producerConnection.close(); consumerSession.close(); consumerConnection.close(); } } protected static final int MAX_RANDOM_BYTE_ARRAY_SIZE_KB = 128; protected static byte[] randomByteArray() { final Random random = new Random(); final byte[] byteArray = new byte[random.nextInt(MAX_RANDOM_BYTE_ARRAY_SIZE_KB * 1024)]; random.nextBytes(byteArray); return byteArray; } protected static void makeWritable(final ActiveMQMessage message) { message.setReadOnlyBody(false); message.setReadOnlyProperties(false); } protected static void simulatePublish(final ActiveMQBytesMessage message) throws JMSException { message.reset(); message.onSend(); } }