//
// 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.xml.sax.SAXException;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.HttpClient;
import org.apache.http.HttpException;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import com.xerox.amazonws.common.AWSException;
import com.xerox.amazonws.common.AWSQueryConnection;
import com.xerox.amazonws.typica.sqs2.jaxb.AddPermissionResponse;
import com.xerox.amazonws.typica.sqs2.jaxb.Attribute;
import com.xerox.amazonws.typica.sqs2.jaxb.ChangeMessageVisibilityResponse;
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.RemovePermissionResponse;
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;
protected String queueId;
private boolean enableEncoding = true;
protected MessageQueue(String queueUrl, String awsAccessId,
String awsSecretKey, boolean isSecure, int port,
String server) throws SQSException {
super(awsAccessId, awsSecretKey, isSecure, server, port);
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
* @throws SQSException wraps checked exceptions
*/
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);
HttpPost method = new HttpPost();
SendMessageResponse response =
makeRequestInt(method, "SendMessage", params, SendMessageResponse.class);
return response.getSendMessageResult().getMessageId();
}
/**
* Attempts to receive a message from the queue. The queue default visibility timeout
* is used.
*
* @return the message object
* @throws SQSException wraps checked exceptions
*/
public Message receiveMessage() throws SQSException {
Message amessage[] = receiveMessages(BigInteger.valueOf(1L), ((BigInteger) (null)), 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
* @throws SQSException wraps checked exceptions
*/
public Message receiveMessage(int visibilityTimeout) throws SQSException {
Message amessage[] = receiveMessages(BigInteger.valueOf(1L), BigInteger.valueOf(visibilityTimeout), null);
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
* @throws SQSException wraps checked exceptions
*/
public Message[] receiveMessages(int numMessages) throws SQSException {
return receiveMessages(BigInteger.valueOf(numMessages), ((BigInteger) (null)), 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
* @throws SQSException wraps checked exceptions
*/
public Message[] receiveMessages(int numMessages, int visibilityTimeout) throws SQSException {
return receiveMessages(BigInteger.valueOf(numMessages), BigInteger.valueOf(visibilityTimeout), 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.
* @param attributes the attributes you'd like to get (SenderId, SentTimestamp, All, ApproximateReceiveCount, ApproximateFirstReceiveTimestamp)
* @return an array of message objects
* @throws SQSException wraps checked exceptions
*/
public Message[] receiveMessages(int numMessages, int visibilityTimeout, List<String> attributes)
throws SQSException {
return receiveMessages(BigInteger.valueOf(numMessages), BigInteger.valueOf(visibilityTimeout), attributes);
}
/**
* 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.
* @param attributes the attributes you'd like to get (SenderId, SentTimestamp)
* @return an array of message objects
* @throws SQSException wraps checked exceptions
*/
protected Message[] receiveMessages(BigInteger numMessages, BigInteger visibilityTimeout, List<String> attributes)
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());
}
if (attributes != null) {
int i=1;
for (String attr : attributes) {
params.put("AttributeName."+i, attr);
i++;
}
}
HttpGet method = new HttpGet();
ReceiveMessageResponse response =
makeRequestInt(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();
Message newMsg = new Message(msg.getMessageId(), msg.getReceiptHandle(), decodedMsg, msg.getMD5OfBody());
for (Attribute attr : msg.getAttributes()) {
newMsg.setAttribute(attr.getName(), attr.getValue());
}
msgs.add(newMsg);
}
return msgs.toArray(new Message [msgs.size()]);
}
}
/**
* Deletes the message identified by message object on the queue this object represents.
*
* @param msg the message to be deleted
* @throws SQSException wraps checked exceptions
*/
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
* @throws SQSException wraps checked exceptions
*/
public void deleteMessage(String receiptHandle) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("ReceiptHandle", receiptHandle);
HttpGet method = new HttpGet();
// DeleteMessageResponse response =
makeRequestInt(method, "DeleteMessage", params, DeleteMessageResponse.class);
}
/**
* Deletes the message queue represented by this object. Will delete non-empty queue.
*
* @throws SQSException wraps checked exceptions
*/
public void deleteQueue() throws SQSException {
Map<String, String> params = new HashMap<String, String>();
HttpGet method = new HttpGet();
// DeleteQueueResponse response =
makeRequestInt(method, "DeleteQueue", params, DeleteQueueResponse.class);
}
/**
* Sets the message visibility timeout.
*
* @param msg the message
* @param timeout the duration (in seconds) the retrieved message is hidden from
* subsequent calls to retrieve.
* @throws SQSException wraps checked exceptions
*/
public void setMessageVisibilityTimeout(Message msg, int timeout) throws SQSException {
setMessageVisibilityTimeout(msg.getReceiptHandle(), timeout);
}
/**
* Sets the message visibility timeout.
*
* @param receiptHandle the handle of the message to be deleted
* @param timeout the duration (in seconds) the retrieved message is hidden from
* subsequent calls to retrieve.
* @throws SQSException wraps checked exceptions
*/
public void setMessageVisibilityTimeout(String receiptHandle, int timeout) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("ReceiptHandle", receiptHandle);
params.put("VisibilityTimeout", ""+timeout);
HttpGet method = new HttpGet();
makeRequestInt(method, "ChangeMessageVisibility", params, ChangeMessageVisibilityResponse.class);
}
/**
* Gets the visibility timeout for the queue. Uses {@link #getQueueAttributes(QueueAttribute)}.
*
* @throws SQSException wraps checked exceptions
*/
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)}.
*
* @throws SQSException wraps checked exceptions
*/
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
* CreatedTimestamp
* LastModifiedTimestamp
* VisibilityTimeout
* RequestPayer
* Policy
*
* @return a map of attributes and their values
* @throws SQSException wraps checked exceptions
*/
public Map<String,String> getQueueAttributes(QueueAttribute qAttr) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("AttributeName", qAttr.queryAttribute());
HttpGet method = new HttpGet();
GetQueueAttributesResponse response =
makeRequestInt(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;
}
/**
* 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.
* @throws SQSException wraps checked exceptions
*/
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
* @throws SQSException wraps checked exceptions
*/
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);
HttpGet method = new HttpGet();
// SetQueueAttributesResponse response =
makeRequestInt(method, "SetQueueAttributes", params, SetQueueAttributesResponse.class);
}
/**
* Adds a permission to this message queue.
*
* @param label a name for this permission
* @param accountId the AWS account ID for the account to share this queue with
* @param action a value to indicate how much to share (SendMessage, ReceiveMessage, ChangeMessageVisibility, DeleteMessage, GetQueueAttributes)
* @throws SQSException wraps checked exceptions
*/
public void addPermission(String label, String accountId, String action) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("Label", label);
params.put("AWSAccountId", accountId);
params.put("ActionName", action);
HttpGet method = new HttpGet();
AddPermissionResponse response =
makeRequestInt(method, "AddPermission", params, AddPermissionResponse.class);
}
/**
* Removes a permission from this message queue.
*
* @param label a name for the permission to be removed
* @throws SQSException wraps checked exceptions
*/
public void removePermission(String label) throws SQSException {
Map<String, String> params = new HashMap<String, String>();
params.put("Label", label);
HttpGet method = new HttpGet();
RemovePermissionResponse response =
makeRequestInt(method, "RemovePermission", params, RemovePermissionResponse.class);
}
/**
* Overriding this because the queue name is baked into the URL and QUERY
* assembles the URL within the baseclass.
*
* @throws SQSException wraps checked exceptions
*/
protected URL makeURL(String resource) throws MalformedURLException {
return super.makeURL(queueId+resource);
}
protected <T> T makeRequestInt(HttpRequestBase method, String action, Map<String, String> params, Class<T> respType)
throws SQSException {
try {
return makeRequest(method, action, params, respType);
} catch (AWSException ex) {
throw new SQSException(ex);
} catch (JAXBException ex) {
throw new SQSException("Problem parsing returned message.", ex);
} catch (SAXException 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);
}
}
public static List<MessageQueue> createList(String [] queueUrls, String awsAccessId,
String awsSecretKey, boolean isSecure, int port, 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, port, server);
mq.setHttpClient(hc);
ret.add(mq);
}
return ret;
}
}