/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* @(#)SysMessageID.java 1.21 06/27/07
*/
package com.sun.messaging.jmq.io;
import java.io.*;
import com.sun.messaging.jmq.net.IPAddress;
import com.sun.messaging.jmq.util.Bits;
/**
* This class encapsulates a system message id. This message id uniquely
* identifies a message in the JMQ system. It consists of four values:
* the message sequence number, source IP address, source port and timestamp.
* Note that this is not the JMS MessageID that can be set by the client.
* <P>
* The Swift packet format specifies that the IP address be in IPv6 (128 bit)
* format, so we canonicalize all addresses to IPv6. Initially we will only
* be dealing with IPv4 addresses since that is all the JDK supports -- those
* get translated into an "IPv4-mapped IPv6" address.
*
*/
public class SysMessageID implements Cloneable {
/**
* Size of a SysMessageID when externalized.
*/
public static final int ID_SIZE = 4 + 4 + 8 + IPAddress.IPV6_SIZE;
private static String ID_PREFIX = "ID:";
protected int sequence;
protected int port;
protected long timestamp;
protected IPAddress ip;
private String msgID = null;
private boolean dirty = true;
transient protected int hashcodeVal = 0;
/**
* Construct an unititialized system message ID. It is assumed
* the caller will set the fields either explicitly or via
* readID()
*/
public SysMessageID() {
ip = new IPAddress();
clear();
}
public static void main(String args[]) {
SysMessageID trystr = new SysMessageID();
System.out.println("With MAC Address");
System.out.println("------------------------");
// get IPAddress
java.net.InetAddress inet = null;
try {
inet = java.net.InetAddress.getLocalHost();
} catch (Exception e) {
}
// set initial sequence, etc
trystr.setIPAddress(inet.getAddress(), IPAddress.getRandomMac());
trystr.setPort(123);
trystr.setTimestamp(123456);
trystr.setSequence(5);
System.out.println("\tOriginal: " + trystr);
SysMessageID newid = get(trystr.toString());
System.out.println("\tDecoded: " + newid);
System.out.println("\tHash[o,n]=[" + trystr.hashCode()
+ "," + newid.hashCode()+"]");
System.out.println("\tEquals: " +(trystr.equals(newid)) +
" , " + (newid.equals(trystr)));
// set initial sequence, etc
System.out.println("\n\nWithout MAC Address");
System.out.println("------------------------");
trystr = new SysMessageID();
trystr.setIPAddress(inet.getAddress());
trystr.setPort(664);
trystr.setTimestamp(789012);
trystr.setSequence(2);
System.out.println("\tOriginal: " + trystr);
newid = get(trystr.toString());
System.out.println("\tHash[o,n]=[" + trystr.hashCode()
+ "," + newid.hashCode()+"]");
System.out.println("\tEquals: "+ (trystr.equals(newid)) +
" , " + (newid.equals(trystr)));
System.out.println("\tDecoded: " + get(trystr.toString()));
System.out.println("\n\nIPV6");
System.out.println("------------------------");
System.out.println("Testing IPV6 requires changes to OS");
/*
trystr = new SysMessageID();
trystr.setIPAddress(inet.getAddress());
trystr.setPort(772);
trystr.setTimestamp(948345);
trystr.setSequence(22);
System.out.println("\tOriginal: " + trystr);
newid = get(trystr.toString());
System.out.println("\tDecoded: " + newid);
System.out.println("\tHash[o,n]=[" + trystr.hashCode()
+ "," + newid.hashCode()+"]");
System.out.println("\tEquals: "+ (trystr.equals(newid)) +
" , " + (newid.equals(trystr)));
System.out.println("\tDecoded: " + get(trystr.toString()));
*/
}
public static SysMessageID get(String sid)
{
SysMessageID id = new SysMessageID();
/*
* Strip out "ID:" if present
*/
if (sid.startsWith(ID_PREFIX)) {
sid = sid.substring(ID_PREFIX.length());
}
// parse string
String[] ss = sid.split("-");
if (ss.length != 4) { // something went wrong
// this is specific case of negative port number (bug 6831547)
// if the port is negative then ss[2] is empty and the length
// of ss array is 5 instead of 4
if (ss.length == 5 && ss[2].length() == 0){
ss[2] = "-" + ss[3];
ss[3] = ss[4];
} else {
throw new RuntimeException("Bad Sysmessage ID");
}
}
int sequence = Integer.parseInt(ss[0]);
int port = Integer.parseInt(ss[2]);
long ts = Long.parseLong(ss[3]);
IPAddress ip = IPAddress.readFromString(ss[1]);
id.setPort(port);
id.setSequence(sequence);
id.setTimestamp(ts);
id.ip = ip;
return id;
}
/**
* Clears the message id
*/
public void clear() {
sequence = port = 0;
timestamp = 0;
ip.clear();
dirty = true;
}
/**
* Check if the passed SysMessageID equals this one
*
* @param o the object to compare
*
* @return true if the objects are equivalent, else false
*/
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SysMessageID)) {
return false;
}
SysMessageID id = (SysMessageID)o;
/*
* We try to optimize the comparison so if it fails it fails
* quickly. Most message id comparisions will be done when
* the router handles acknowledgements from a client. In that
* case the sequence number is the most likely to determine
* uniqueness
*/
return (
this.sequence == id.sequence &&
this.timestamp == id.timestamp &&
this.port == id.port &&
ip.equals(id.ip)
);
}
public int hashCode() {
// This should generate enough uniqueness without messing with
// the IP address.
if(hashcodeVal==0){
int p = (port == 0 ? 1 : port);
hashcodeVal = (int)timestamp * p * sequence;
}
return hashcodeVal;
}
/**
* Return a string description. The string will be of the format:
*
* nnn-nnn.nnn.nnn.nnn-nnnn-nnnnnnnnnnnn
* ^ ^ ^ ^
* seq IP port timestamp
*
* @return String description of message id
*
*/
public String toString() {
if (msgID == null || dirty) {
msgID = new StringBuffer(128)
.append(sequence).append("-")
.append(ip.toString()).append("-")
.append(port).append("-")
.append(timestamp)
.toString();
dirty = false;
}
return msgID;
}
/**
* Generates a unique string that is suitable for use as a key.
*/
public String getUniqueName() {
return toString();
}
/**
* Returns the unique string of msg ID prior to MQ 4.1.
*
* Note: should only be used for store migration
*/
public String getUniqueNameOldFormat() {
String uniqueName = String.valueOf(sequence) +
String.valueOf(timestamp) +
String.valueOf(port) +
IPAddress.rawIPToString(ip.getAddress(), false);
return uniqueName;
}
public int getSequence() {
return sequence;
}
public int getPort() {
return port;
}
public long getTimestamp() {
return timestamp;
}
/**
* Get the IP address.
*
* @return The raw IP address. This will always be an IPv6 (128 bit)
* address. It will contain an embedded Mac address if one
* was provided to setIPAddress().
*/
public byte[] getIPAddress() {
return ip.getAddress();
}
public void setSequence(int n) {
dirty = true;
sequence = n;
}
public void setPort(int n) {
dirty = true;
port = n;
}
public void setTimestamp(long n) {
dirty = true;
timestamp = n;
}
/**
* Set the IP address.
*
* @param newip IP address in network byte order. This can be the
* buffer returned by InetAddress.getAddress().
* A null value results in the IP address being cleared.
*
*/
public void setIPAddress(byte[] newip)
throws IllegalArgumentException {
ip.setAddress(newip);
dirty = true;
}
/**
* Set the IP address and a 48 bit mac addres.
*
* @param newip IP address in network byte order. This can be the
* buffer returned by InetAddress.getAddress().
* A null value results in the IP address being cleared.
*
* @param mac 6 byte MAC address (or random psuedo address. You
* can get it from IPAddress.getRandomMac())
*
*/
public void setIPAddress(byte[] newip, byte[] mac)
throws IllegalArgumentException {
ip.setAddress(newip);
if (mac != null && ip.getType() != IPAddress.IPV6) {
// Only set mac if we are not IPV6
ip.setMac(mac);
}
dirty = true;
}
/**
* Write the ID to the specified DataOutput.
* The format of the written data will be:
*
* <PRE>
* 0 1 2 3
* |0 1 2 3 4 5 6 7|8 9 0 1 2 3 4 5|6 7 8 9 0 1 2 3|4 5 6 7 8 9 0 1|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + timestamp +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | source IP |
* + +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | source port |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* </PRE>
*
* This format matches the format specified in the ACKNOWLEDGE message
* of the JMQ protocol spec.
*
* We don't use ObjectSerialization to do this because we don't want
* the class version cruft that would be prefixed to the data.
*
* @param out DataOutput to write ID to
*/
public void writeID(DataOutput out)
throws IOException {
out.writeLong(timestamp);
ip.writeAddress(out);
out.writeInt(port);
out.writeInt(sequence);
if (out instanceof DataOutputStream) {
((DataOutputStream)out).flush();
}
}
/**
* Returns the ID in a raw format. The format will be as described
* in writeID, and the size of the byte[] will be ID_SIZE
*/
public byte[] getRawID() {
byte[] buf = new byte[ID_SIZE];
int i = 0;
i = Bits.put(buf, i, timestamp);
i = Bits.put(buf, i, ip.getAddressUnsafe());
i = Bits.put(buf, i, port);
Bits.put(buf, i, sequence);
return buf;
}
/**
* Read the ID from the specified DataInput. The format of
* the data is assumed to match that generated by writeID.
*
* @param in DataInput to write ID to
*
*/
public void readID(DataInput in)
throws IOException {
timestamp = in.readLong();
ip.readAddress(in);
port = in.readInt();
sequence = in.readInt();
}
/**
* Make a deep copy of this object
*/
public Object clone() {
try {
SysMessageID newID = (SysMessageID)super.clone();
newID.ip = (IPAddress)this.ip.clone();
return newID;
} catch (CloneNotSupportedException e) {
// Should never get this, but don't fail silently
System.out.println("SysMessageID: Could not clone: " + e);
return null;
}
}
}