//
// typica - A client library for Amazon Web Services
// Copyright (C) 2007 Xerox Corporation
//
// Licensed 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 com.xerox.amazonws.sqs2;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import com.xerox.amazonws.common.AWSQueryConnection;
import com.xerox.amazonws.typica.sqs2.jaxb.Attribute;
import com.xerox.amazonws.typica.sqs2.jaxb.DeleteMessageResponse;
import com.xerox.amazonws.typica.sqs2.jaxb.DeleteQueueResponse;
import com.xerox.amazonws.typica.sqs2.jaxb.GetQueueAttributesResponse;
import com.xerox.amazonws.typica.sqs2.jaxb.ReceiveMessageResponse;
import com.xerox.amazonws.typica.sqs2.jaxb.SendMessageResponse;
import com.xerox.amazonws.typica.sqs2.jaxb.SetQueueAttributesResponse;
/**
* This class provides an interface with the Amazon SQS message queue. It provides methods
* for sending / receiving messages and deleting queues and messsages on queues.
*
* @author D. Kavanagh
* @author developer@dotech.com
*/
public class MessageQueue extends AWSQueryConnection {
public static final int MAX_MESSAGES = 600;
public static final int MAX_MESSAGE_BODIES_SIZE = 4096;
protected String queueId;
private boolean enableEncoding = true;
protected MessageQueue(String queueUrl, String awsAccessId,
String awsSecretKey, boolean isSecure,
String server) throws SQSException {
super(awsAccessId, awsSecretKey, isSecure, server, isSecure ? 443 : 80);
if (queueUrl.startsWith("http")) {
queueId = queueUrl.substring(queueUrl.indexOf("//")+2);
}
else {
queueId = queueUrl; // this is the case where the queue is created from a
// fully qualified queue name, not a full queue URL
}
queueId = queueId.substring(queueId.indexOf("/")+1);
QueueService.setVersionHeader(this);
}
/**
* This method provides the URL for the message queue represented by this object.
*
* @return generated queue service url
*/
public URL getUrl() {
try {
return new URL(super.getUrl().toString());
} catch (MalformedURLException ex) {
return null;
}
}
/**
* This method returns the state of the base64 encoding flag. By default, all messages
* are encoded on send and decoded on receive.
*
* @return state of encoding flag
*/
public boolean isEncoding() {
return enableEncoding;
}
/**
* This method sets the state of the encoding flag. Use this to override the default and
* turn off automatic base64 encoding.
*
* @param enable the new state of the encoding flag
*/
public void setEncoding(boolean enable) {
enableEncoding = enable;
}
/**
* Sends a message to a specified queue. The message must be between 1 and 256K bytes long.
*
* @param msg the message to be sent
*/
public String sendMessage(String msg) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
String encodedMsg = enableEncoding?new String(Base64.encodeBase64(msg.getBytes())):msg;
params.put("MessageBody", encodedMsg);
PostMethod method = new PostMethod();
try {
SendMessageResponse response =
makeRequest(method, "SendMessage", params, SendMessageResponse.class);
return response.getSendMessageResult().getMessageId();
} catch (JAXBException ex) {
throw new SQSException("Problem parsing returned message.", ex);
} catch (HttpException ex) {
throw new SQSException(ex.getMessage(), ex);
} catch (IOException ex) {
throw new SQSException(ex.getMessage(), ex);
} finally {
method.releaseConnection();
}
}
/**
* Attempts to receive a message from the queue. The queue default visibility timeout
* is used.
*
* @return the message object
*/
public Message receiveMessage() throws SQSException {
Message amessage[] = receiveMessages(BigInteger.valueOf(1L), ((BigInteger) (null)));
if(amessage.length > 0)
return amessage[0];
else
return null;
}
/**
* Attempts to receive a message from the queue.
*
* @param visibilityTimeout the duration (in seconds) the retrieved message is hidden from
* subsequent calls to retrieve.
* @return the message object
*/
public Message receiveMessage(int visibilityTimeout) throws SQSException {
Message amessage[] = receiveMessages(BigInteger.valueOf(1L), BigInteger.valueOf(visibilityTimeout));
if(amessage.length > 0)
return amessage[0];
else
return null;
}
/**
* Attempts to retrieve a number of messages from the queue. If less than that are availble,
* the max returned is the number of messages in the queue, but not necessarily all messages
* in the queue will be returned. The queue default visibility timeout is used.
*
* @param numMessages the maximum number of messages to return
* @return an array of message objects
*/
public Message[] receiveMessages(int numMessages) throws SQSException {
return receiveMessages(BigInteger.valueOf(numMessages), ((BigInteger) (null)));
}
/**
* Attempts to retrieve a number of messages from the queue. If less than that are availble,
* the max returned is the number of messages in the queue, but not necessarily all messages
* in the queue will be returned.
*
* @param numMessages the maximum number of messages to return
* @param visibilityTimeout the duration (in seconds) the retrieved message is hidden from
* subsequent calls to retrieve.
* @return an array of message objects
*/
public Message[] receiveMessages(int numMessages, int visibilityTimeout) throws SQSException {
return receiveMessages(BigInteger.valueOf(numMessages), BigInteger.valueOf(visibilityTimeout));
}
/**
* Internal implementation of receiveMessages.
*
* @param numMessages the maximum number of messages to return
* @param visibilityTimeout the duration (in seconds) the retrieved message is hidden from
* subsequent calls to retrieve.
* @return an array of message objects
*/
protected Message[] receiveMessages(BigInteger numMessages, BigInteger visibilityTimeout) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
if (numMessages != null) {
params.put("MaxNumberOfMessages", numMessages.toString());
}
if (visibilityTimeout != null) {
params.put("VisibilityTimeout", visibilityTimeout.toString());
}
GetMethod method = new GetMethod();
try {
ReceiveMessageResponse response =
makeRequest(method, "ReceiveMessage", params, ReceiveMessageResponse.class);
if (response.getReceiveMessageResult().getMessages() == null) {
return new Message[0];
}
else {
ArrayList<Message> msgs = new ArrayList();
for (com.xerox.amazonws.typica.sqs2.jaxb.Message msg : response.getReceiveMessageResult().getMessages()) {
String decodedMsg = enableEncoding?
new String(Base64.decodeBase64(msg.getBody().getBytes())):
msg.getBody();
msgs.add(new Message(msg.getMessageId(), msg.getReceiptHandle(), decodedMsg, msg.getMD5OfBody()));
}
return msgs.toArray(new Message [msgs.size()]);
}
} catch (JAXBException ex) {
throw new SQSException("Problem parsing returned message.", ex);
} catch (HttpException ex) {
throw new SQSException(ex.getMessage(), ex);
} catch (IOException ex) {
throw new SQSException(ex.getMessage(), ex);
} finally {
method.releaseConnection();
}
}
/**
* Deletes the message identified by message object on the queue this object represents.
*
* @param msg the message to be deleted
*/
public void deleteMessage(Message msg) throws SQSException {
deleteMessage(msg.getReceiptHandle());
}
/**
* Deletes the message identified by receiptHandle on the queue this object represents.
*
* @param receiptHandle the handle of the message to be deleted
*/
public void deleteMessage(String receiptHandle) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("ReceiptHandle", receiptHandle);
GetMethod method = new GetMethod();
try {
//DeleteMessageResponse response =
makeRequest(method, "DeleteMessage", params, DeleteMessageResponse.class);
} catch (JAXBException ex) {
throw new SQSException("Problem parsing returned message.", ex);
} catch (HttpException ex) {
throw new SQSException(ex.getMessage(), ex);
} catch (IOException ex) {
throw new SQSException(ex.getMessage(), ex);
} finally {
method.releaseConnection();
}
}
/**
* Deletes the message queue represented by this object. Will delete non-empty queue.
*/
public void deleteQueue() throws SQSException {
Map<String, String> params = new HashMap<String, String>();
GetMethod method = new GetMethod();
try {
//DeleteQueueResponse response =
makeRequest(method, "DeleteQueue", params, DeleteQueueResponse.class);
} catch (JAXBException ex) {
throw new SQSException("Problem parsing returned message.", ex);
} catch (HttpException ex) {
throw new SQSException(ex.getMessage(), ex);
} catch (IOException ex) {
throw new SQSException(ex.getMessage(), ex);
} finally {
method.releaseConnection();
}
}
/**
* Gets the visibility timeout for the queue. Uses {@link #getQueueAttributes(QueueAttribute)}.
*/
public int getVisibilityTimeout() throws SQSException {
return Integer.parseInt(getQueueAttributes(QueueAttribute.VISIBILITY_TIMEOUT)
.values().iterator().next());
}
/**
* Gets the visibility timeout for the queue. Uses {@link #getQueueAttributes(QueueAttribute)}.
*/
public int getApproximateNumberOfMessages() throws SQSException {
return Integer.parseInt(getQueueAttributes(QueueAttribute.APPROXIMATE_NUMBER_OF_MESSAGES)
.values().iterator().next());
}
/**
* Gets queue attributes. This is provided to expose the underlying functionality.
* Currently supported attributes are ApproximateNumberOfMessages and VisibilityTimeout.
*
* @return a map of attributes and their values
*/
public Map<String,String> getQueueAttributes(QueueAttribute qAttr) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("AttributeName", qAttr.queryAttribute());
GetMethod method = new GetMethod();
try {
GetQueueAttributesResponse response =
makeRequest(method, "GetQueueAttributes", params, GetQueueAttributesResponse.class);
Map<String,String> ret = new HashMap<String,String>();
List<Attribute> attrs = response.getGetQueueAttributesResult().getAttributes();
for (Attribute attr : attrs) {
ret.put(attr.getName(), attr.getValue());
}
return ret;
} catch (JAXBException ex) {
throw new SQSException("Problem getting the visilibity timeout.", ex);
} catch (HttpException ex) {
throw new SQSException(ex.getMessage(), ex);
} catch (IOException ex) {
throw new SQSException(ex.getMessage(), ex);
} finally {
method.releaseConnection();
}
}
/**
* Sets the visibility timeout of the queue. Uses {@link #setQueueAttribute(String, String)}.
*
* @param timeout the duration (in seconds) the retrieved message is hidden from
* subsequent calls to retrieve.
*/
public void setVisibilityTimeout(int timeout) throws SQSException {
setQueueAttribute("VisibilityTimeout", ""+timeout);
}
/**
* Sets a queue attribute. This is provided to expose the underlying functionality, although
* the only attribute at this time is visibility timeout.
*
* @param attribute name of the attribute being set
* @param value the value being set for this attribute
*/
public void setQueueAttribute(String attribute, String value) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("Attribute.Name", attribute);
params.put("Attribute.Value", value);
GetMethod method = new GetMethod();
try {
//SetQueueAttributesResponse response =
makeRequest(method, "SetQueueAttributes", params, SetQueueAttributesResponse.class);
} catch (JAXBException ex) {
throw new SQSException("Problem setting the visibility timeout.", ex);
} catch (HttpException ex) {
throw new SQSException(ex.getMessage(), ex);
} catch (IOException ex) {
throw new SQSException(ex.getMessage(), ex);
} finally {
method.releaseConnection();
}
}
/**
* Overriding this because the queue name is baked into the URL and QUERY
* assembles the URL within the baseclass.
*/
protected URL makeURL(String resource) throws MalformedURLException {
return super.makeURL(queueId+resource);
}
public static List<MessageQueue> createList(String [] queueUrls, String awsAccessId,
String awsSecretKey, boolean isSecure, String server, HttpClient hc)
throws SQSException {
ArrayList<MessageQueue> ret = new ArrayList<MessageQueue>();
for (int i=0; i<queueUrls.length; i++) {
MessageQueue mq = new MessageQueue(queueUrls[i], awsAccessId, awsSecretKey, isSecure, server);
mq.setHttpClient(hc);
ret.add(mq);
}
return ret;
}
}