package org.openamq.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.mina.common.ByteBuffer; import org.openamq.client.protocol.AMQProtocolHandler; import org.openamq.client.state.listener.SpecificMethodFrameListener; import org.openamq.client.message.AbstractJMSMessage; import org.openamq.client.message.JMSBytesMessage; import org.openamq.AMQException; import org.openamq.framing.*; import javax.jms.*; import java.io.UnsupportedEncodingException; public class BasicMessageProducer extends Closeable implements org.openamq.jms.MessageProducer { protected final Logger _logger = LoggerFactory.getLogger(getClass()); private AMQConnection _connection; /** * If true, messages will not get a timestamp. */ private boolean _disableTimestamps; /** * Priority of messages created by this producer. */ private int _messagePriority; /** * Time to live of messages. Specified in milliseconds but AMQ has 1 second resolution. */ private long _timeToLive; /** * Delivery mode used for this producer. */ private int _deliveryMode = DeliveryMode.PERSISTENT; /** * The Destination used for this consumer, if specified upon creation. */ protected AMQDestination _destination; /** * Default encoding used for messages produced by this producer. */ private String _encoding; /** * Default encoding used for message produced by this producer. */ private String _mimeType; private AMQProtocolHandler _protocolHandler; /** * True if this producer was created from a transacted session */ private boolean _transacted; private int _channelId; /** * This is an id generated by the session and is used to tie individual producers to the session. This means we * can deregister a producer with the session when the producer is clsoed. We need to be able to tie producers * to the session so that when an error is propagated to the session it can close the producer (meaning that * a client that happens to hold onto a producer reference will get an error if he tries to use it subsequently). */ private long _producerId; /** * The session used to create this producer */ private AMQSession _session; private final boolean _immediate; private final boolean _mandatory; private final boolean _waitUntilSent; protected BasicMessageProducer(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId, AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory, boolean waitUntilSent) throws AMQException { _connection = connection; _destination = destination; _transacted = transacted; _protocolHandler = protocolHandler; _channelId = channelId; _session = session; _producerId = producerId; if (destination != null) { declareDestination(destination); } _immediate = immediate; _mandatory = mandatory; _waitUntilSent = waitUntilSent; } void resubscribe() throws AMQException { if (_destination != null) { declareDestination(_destination); } } private void declareDestination(AMQDestination destination) throws AMQException { // Declare the exchange // Note that the durable and internal arguments are ignored since passive is set to false AMQFrame declare = ExchangeDeclareBody.createAMQFrame(_channelId, 0, destination.getExchangeName(), destination.getExchangeClass(), false, false, false, false, true, null); _protocolHandler.writeFrame(declare); } public void setDisableMessageID(boolean b) throws JMSException { checkNotClosed(); // IGNORED } public boolean getDisableMessageID() throws JMSException { checkNotClosed(); // Always false for OpenAMQ return false; } public void setDisableMessageTimestamp(boolean b) throws JMSException { checkNotClosed(); _disableTimestamps = b; } public boolean getDisableMessageTimestamp() throws JMSException { checkNotClosed(); return _disableTimestamps; } public void setDeliveryMode(int i) throws JMSException { checkNotClosed(); if (i != DeliveryMode.NON_PERSISTENT && i != DeliveryMode.PERSISTENT) { throw new JMSException("DeliveryMode must be either NON_PERSISTENT or PERSISTENT. Value of " + i + " is illegal"); } _deliveryMode = i; } public int getDeliveryMode() throws JMSException { checkNotClosed(); return _deliveryMode; } public void setPriority(int i) throws JMSException { checkNotClosed(); if (i < 0 || i > 9) { throw new IllegalArgumentException("Priority of " + i + " is illegal. Value must be in range 0 to 9"); } _messagePriority = i; } public int getPriority() throws JMSException { checkNotClosed(); return _messagePriority; } public void setTimeToLive(long l) throws JMSException { checkNotClosed(); if (l < 0) { throw new IllegalArgumentException("Time to live must be non-negative - supplied value was " + l); } _timeToLive = l; } public long getTimeToLive() throws JMSException { checkNotClosed(); return _timeToLive; } public Destination getDestination() throws JMSException { checkNotClosed(); return _destination; } public void close() throws JMSException { _closed.set(true); _session.deregisterProducer(_producerId); } public void send(Message message) throws JMSException { synchronized (_connection.getFailoverMutex()) { sendImpl(_destination, (AbstractJMSMessage) message, _deliveryMode, _messagePriority, _timeToLive, _mandatory, _immediate); } } public void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException { synchronized (_connection.getFailoverMutex()) { sendImpl(_destination, (AbstractJMSMessage)message, deliveryMode, priority, timeToLive, _mandatory, _immediate); } } public void send(Destination destination, Message message) throws JMSException { checkNotClosed(); synchronized (_connection.getFailoverMutex()) { validateDestination(destination); sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, _deliveryMode, _messagePriority, _timeToLive, _mandatory, _immediate); } } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException { checkNotClosed(); synchronized (_connection.getFailoverMutex()) { validateDestination(destination); sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, _mandatory, _immediate); } } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, boolean mandatory) throws JMSException { checkNotClosed(); synchronized (_connection.getFailoverMutex()) { validateDestination(destination); sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, mandatory, _immediate); } } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate) throws JMSException { checkNotClosed(); synchronized (_connection.getFailoverMutex()) { validateDestination(destination); sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, mandatory, immediate); } } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException { checkNotClosed(); synchronized (_connection.getFailoverMutex()) { validateDestination(destination); sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, mandatory, immediate, waitUntilSent); } } private void validateDestination(Destination destination) throws JMSException { if (!(destination instanceof AMQDestination)) { throw new JMSException("Unsupported destination class: " + (destination != null?destination.getClass():null)); } try { declareDestination((AMQDestination)destination); } catch (AMQException e) { throw new JMSException("Unable to declare destination " + destination + ": " + e); } } protected void sendImpl(AMQDestination destination, AbstractJMSMessage message, int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate) throws JMSException { sendImpl(destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, _waitUntilSent); } /** * The caller of this method must hold the failover mutex. * @param destination * @param message * @param deliveryMode * @param priority * @param timeToLive * @param mandatory * @param immediate * @throws JMSException */ protected void sendImpl(AMQDestination destination, AbstractJMSMessage message, int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate, boolean wait) throws JMSException { AMQFrame publishFrame = BasicPublishBody.createAMQFrame(_channelId, 0, destination.getExchangeName(), destination.getRoutingKey(), mandatory, immediate); long currentTime = 0; if (!_disableTimestamps) { currentTime = System.currentTimeMillis(); message.setJMSTimestamp(currentTime); } // // Very nasty temporary hack for GRM-206. Will be altered ASAP. // if(message instanceof JMSBytesMessage) { JMSBytesMessage msg = (JMSBytesMessage) message; if(!msg.isReadable()) { msg.reset(); } } ByteBuffer payload = message.getData(); BasicContentHeaderProperties contentHeaderProperties = message.getJmsContentHeaderProperties(); if (timeToLive > 0) { if (!_disableTimestamps) { contentHeaderProperties.setExpiration(currentTime + timeToLive); } } else { if (!_disableTimestamps) { contentHeaderProperties.setExpiration(0); } } contentHeaderProperties.setDeliveryMode((byte) deliveryMode); contentHeaderProperties.setPriority((byte) priority); int size = payload.limit(); ContentBody[] contentBodies = createContentBodies(payload); AMQFrame[] frames = new AMQFrame[2 + contentBodies.length]; for (int i = 0; i < contentBodies.length; i++) { frames[2 + i] = ContentBody.createAMQFrame(_channelId, contentBodies[i]); } if (contentBodies.length > 0 && _logger.isDebugEnabled()) { _logger.debug("Sending content body frames to " + destination); } // weight argument of zero indicates no child content headers, just bodies AMQFrame contentHeaderFrame = ContentHeaderBody.createAMQFrame(_channelId, BasicConsumeBody.CLASS_ID, 0, contentHeaderProperties, size); if (_logger.isDebugEnabled()) { _logger.debug("Sending content header frame to " + destination); } frames[0] = publishFrame; frames[1] = contentHeaderFrame; CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames); _protocolHandler.writeFrame(compositeFrame, wait); } /** * Create content bodies. This will split a large message into numerous bodies depending on the negotiated * maximum frame size. * @param payload * @return the array of content bodies */ private ContentBody[] createContentBodies(ByteBuffer payload) { if (payload == null) { return null; } else if (payload.remaining() == 0) { return new ContentBody[0]; } // we substract one from the total frame maximum size to account for the end of frame marker in a body frame // (0xCE byte). int dataLength = payload.remaining(); final long framePayloadMax = _session.getAMQConnection().getMaximumFrameSize() - 1; int lastFrame = (dataLength % framePayloadMax) > 0 ? 1 : 0; int frameCount = (int) (dataLength/framePayloadMax) + lastFrame; final ContentBody[] bodies = new ContentBody[frameCount]; if (frameCount == 1) { bodies[0] = new ContentBody(); bodies[0].payload = payload; } else { long remaining = dataLength; for (int i = 0; i < bodies.length; i++) { bodies[i] = new ContentBody(); payload.position((int)framePayloadMax * i); int length = (remaining >= framePayloadMax) ? (int)framePayloadMax : (int)remaining; payload.limit(payload.position() + length); bodies[i].payload = payload.slice(); remaining -= length; } } return bodies; } public void setMimeType(String mimeType) { checkNotClosed(); _mimeType = mimeType; } public void setEncoding(String encoding) throws UnsupportedEncodingException { checkNotClosed(); _encoding = encoding; } }