package com.cloudhopper.mq.message;
/*
* #%L
* ch-mq
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/
import java.nio.ByteBuffer;
import com.cloudhopper.mq.util.Priority;
import com.cloudhopper.mq.util.Tiny;
/**
* Implementation of an MQMessage that includes Priority. For use with priority queues.
*
* @author garth
*/
public class PriorityMQMessage<T> extends AttemptCountingMQMessage<T> implements Comparable<PriorityMQMessage> {
public PriorityMQMessage() {
super();
}
public PriorityMQMessage(Priority priority, T body) {
super();
this.priority = priority;
this.body = body;;
}
public PriorityMQMessage(short transferAttempts,
short transfers,
String logItemIdentifier,
Priority priority) {
super(transferAttempts, transfers, logItemIdentifier);
this.priority = priority;
}
public PriorityMQMessage(short transferAttempts,
short transfers,
String logItemIdentifier,
Priority priority,
long ts,
int sequence) {
super(transferAttempts, transfers, logItemIdentifier);
setPriority(priority);
//TODO: need to rethink this. allowing external setting of the sequence means that we may have
// colliding keys (e.g. when decoding messages from 2 different remote brokers)
setSequence(sequence);
setTimestamp(ts);
}
private Priority priority;
private Tiny sequence = new Tiny(0);
private long ts = 0L;
private static final long SIX_BYTE_LONG = 140737488355328L;
/**
* For priority messaging, if the message should be treated as a
* "first in line" priority message. Actual algorithm for priority is
* left up to the queue implementation.
*/
public Priority getPriority() {
return this.priority;
}
/**
* Sets the priority of the message.
*/
public void setPriority(Priority priority) {
this.priority = priority;
}
/**
* Gets the timestamp of the message
*/
public long getTimestamp() {
return this.ts;
}
/**
* Set the timestamp of the message when it was created. Doesn't allow longs greater than 6 bytes
*/
public void setTimestamp(long ts) {
if (ts > SIX_BYTE_LONG || ts < 0) throw new IllegalArgumentException("Ts must be 0-140737488355328");
this.ts = ts;
}
/**
* Gets the sequence number
*/
public int getSequence() {
return this.sequence.intValue();
}
/**
* Sets the sequence number. Will throw an IllegalArgumentException for values > 255
*/
public void setSequence(int seq) {
this.sequence = new Tiny(seq);
}
//////////////////////////////////////
//
// Utilities for DefaultPriorityQueue
// these values are not persisted
//
//////////////////////////////////////
public boolean isTimestampSet() {
return (this.ts > 0);
}
public boolean isSequenceSet() {
return (this.sequence != null);
}
// TODO: use inner class Key below
public long key() {
byte[] k = new byte[8];
ByteBuffer buf = ByteBuffer.wrap(k);
buf.put(priority.byteValue());
for (int i = 0; i < 6; i++) {
buf.put( (byte)(ts >>> ((6-1-i) * 8)) );
}
buf.put(sequence.byteValue());
return ByteBuffer.wrap(k).getLong();
}
/**
* Compares priority of two PriorityMQMessage, first by priority, then by key (ts+seq).
*/
@Override public int compareTo(PriorityMQMessage o) {
int diff = getPriority().intValue() - o.getPriority().intValue();
if (diff == 0) {
long sdiff = key() - o.key();
if (sdiff > 0) return 1;
else if (sdiff < 0) return -1;
else return 0;
} else {
return diff;
}
}
@Override public String toString() {
StringBuilder o = new StringBuilder();
o.append("PriorityMQMessage<");
if (getBody() != null) o.append(getBody().getClass().getName());
else o.append("?");
o.append("> ");
o.append("( transferAttempts=").append(transferAttempts).append(", ");
o.append("transfers=").append(transfers).append(", ");
o.append("logItemIdentifier=").append(logItemIdentifier).append(", ");
o.append("priority=").append(priority).append(", ");
o.append("sequence=").append(sequence).append(", ");
o.append("timestamp=").append(ts).append(" ) ");
return o.toString();
}
public static class Key {
public Key(long k) {
this(ByteBuffer.wrap(new byte[8]).putLong(k).array());
}
public Key(byte[] k) {
ByteBuffer buf = ByteBuffer.wrap(k);
this._priority = new Priority(buf.get());
long value = 0;
byte[] b = new byte[6];
buf.get(b);
for (int i = 0; i < b.length; i++) {
value = (value << 8) + (b[i] & 0xff);
}
this._timestamp = value;
this._sequence = new Tiny(buf.get());
}
public Key(Priority p, long t, Tiny s) {
this._priority = p;
this._timestamp = t;
this._sequence = s;
}
private final Priority _priority;
private final long _timestamp;
private final Tiny _sequence;
public Priority getPriority() { return this._priority; }
public long getTimestamp() { return this._timestamp; }
public Tiny getSequence() { return this._sequence; }
public long getKey() {
byte[] k = new byte[8];
ByteBuffer buf = ByteBuffer.wrap(k);
buf.put(_priority.byteValue());
for (int i = 0; i < 6; i++) {
buf.put( (byte)(_timestamp >>> ((6-1-i) * 8)) );
}
buf.put(_sequence.byteValue());
return ByteBuffer.wrap(k).getLong();
}
public String toString() {
StringBuilder o = new StringBuilder();
o.append("PriorityMQMessage.Key (");
o.append("priority=").append(_priority).append(", ");
o.append("timestamp=").append(_timestamp).append(", ");
o.append("sequence=").append(_sequence).append(")");
return o.toString();
}
public String toCompactString(String prefix) {
StringBuilder o = new StringBuilder(prefix);
o.append(_priority).append(':');
o.append(_timestamp).append(':');
o.append(_sequence);
return o.toString();
}
public static Key fromCompactString(String s) {
String[] es = s.split(":");
if (es.length != 3) throw new IllegalArgumentException("Must be compact string representation of Key.");
return new Key(new Priority(Integer.parseInt(es[0])),
Long.parseLong(es[1]),
new Tiny(Integer.parseInt(es[2])));
}
}
}