001 /** 002 * 003 * Copyright 2004 Hiram Chirino 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 **/ 018 package org.activemq.message; 019 020 import org.apache.commons.logging.Log; 021 import org.apache.commons.logging.LogFactory; 022 023 import javax.jms.JMSException; 024 import javax.transaction.xa.Xid; 025 import java.io.*; 026 027 /** 028 * <P> 029 * A <CODE>ActiveMQXid</CODE> object holds the distributed 030 * transaction id that is passed around in an ActiveMQ system. 031 * <P> 032 * Eventhough a Transaction Manager (TM) has his own Xid implementation 033 * that he uses when he talks to the our ActiveMQXAResource, we need to 034 * send the Xid data down to the server in our format. 035 * <P> 036 * ActiveMQ uses Strings as the transaction id. This class coverts an 037 * Xid to and from a string. 038 * <p/> 039 * <P> 040 * 041 * @version $Revision: 1.1.1.1 $ 042 * @see javax.transaction.xa.Xid 043 */ 044 public class ActiveMQXid implements Xid, Externalizable, Comparable { 045 private static final long serialVersionUID = -5754338187296859149L; 046 private static final Log log = LogFactory.getLog(ActiveMQXid.class); 047 048 private int formatId; 049 private byte[] branchQualifier; 050 private byte[] globalTransactionId; 051 private transient int hash; 052 053 private static final String[] HEX_TABLE = new String[]{ 054 "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", 055 "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", 056 "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", 057 "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", 058 "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", 059 "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", 060 "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", 061 "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", 062 "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", 063 "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", 064 "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", 065 "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", 066 "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", 067 "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", 068 "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", 069 "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff", 070 }; 071 072 073 /** 074 * Deserializes the data into an Xid 075 * 076 * @param data 077 * @return 078 */ 079 public static ActiveMQXid fromBytes(byte[] data) throws IOException { 080 return read(new DataInputStream(new ByteArrayInputStream(data))); 081 } 082 083 /** 084 * A helper method for the OpenWire protocol to convert a local or XA transaction ID object into a String 085 */ 086 public static String transactionIDToString(Object transactionID) throws IOException { 087 if (transactionID == null) { 088 return ""; 089 } 090 else if (transactionID instanceof String) { 091 return (String) transactionID; 092 } 093 else if (transactionID instanceof ActiveMQXid) { 094 ActiveMQXid xid = (ActiveMQXid) transactionID; 095 return "XID:" + toHexFromBytes(xid.toBytes()); 096 } 097 else { 098 return transactionID.toString(); 099 } 100 } 101 102 /** 103 * A helper method for the OpenWire protocol to convert a local or XA transaction ID string into an object 104 */ 105 public static Object transactionIDFromString(String text) throws IOException { 106 if (text == null || text.length() == 0) { 107 return null; 108 } 109 if (text.startsWith("XID:")) { 110 return new ActiveMQXid(toBytesFromHex(text.substring(4))); 111 112 } 113 return text; 114 } 115 116 /** 117 * This constructor is only used for serialization 118 */ 119 public ActiveMQXid() { 120 } 121 122 /** 123 * Creates a new ActiveMQXid object with the Xid data 124 * contained in <code>xid</code> 125 */ 126 public ActiveMQXid(Xid xid) { 127 this.formatId = xid.getFormatId(); 128 this.branchQualifier = xid.getBranchQualifier(); 129 this.globalTransactionId = xid.getGlobalTransactionId(); 130 } 131 132 public ActiveMQXid(int formatId, byte[] branchQualifier, byte[] globalTransactionId) { 133 this.formatId = formatId; 134 this.branchQualifier = branchQualifier; 135 this.globalTransactionId = globalTransactionId; 136 } 137 138 public ActiveMQXid(byte[] data) throws IOException { 139 readState(new DataInputStream(new ByteArrayInputStream(data))); 140 } 141 142 /** 143 * Creates a new ActiveMQXid object. 144 */ 145 public ActiveMQXid(String txid) throws JMSException { 146 String parts[] = txid.split(":", 3); 147 if (parts.length != 3) { 148 throw new JMSException("Invalid XID: " + txid); 149 } 150 formatId = Integer.parseInt(parts[0]); 151 152 if (log.isDebugEnabled()) { 153 log.debug("parts:" + parts[0]); 154 log.debug("parts:" + parts[1]); 155 log.debug("parts:" + parts[2]); 156 } 157 globalTransactionId = toBytesFromHex(parts[1]); 158 branchQualifier = toBytesFromHex(parts[2]); 159 } 160 161 public int hashCode() { 162 if (hash == 0) { 163 hash = formatId; 164 hash = hash(branchQualifier, hash); 165 hash = hash(globalTransactionId, hash); 166 } 167 if (hash == 0) { 168 hash = 0xaceace; 169 } 170 return hash; 171 } 172 173 public boolean equals(Object that) { 174 if (this == that) { 175 return true; 176 } 177 else if (hashCode() == that.hashCode() && that instanceof Xid) { 178 return equals(this, (Xid)that); 179 } 180 return false; 181 } 182 183 /** 184 * Test for equivlance between two Xid 185 * @param tis 186 * @param that 187 * @return 188 */ 189 public static boolean equals(Xid tis,Xid that) { 190 if ( tis == that){ 191 return true; 192 }else if (tis == null || that == null){ 193 return false; 194 } 195 return tis.getFormatId() == that.getFormatId() && equals(tis.getBranchQualifier(), that.getBranchQualifier()) && equals(tis.getGlobalTransactionId(), that.getGlobalTransactionId()); 196 } 197 198 public int compareTo(Object object) { 199 if (this == object) { 200 return 0; 201 } 202 else { 203 if (object instanceof ActiveMQXid) { 204 ActiveMQXid that = (ActiveMQXid) object; 205 int diff = this.formatId - that.formatId; 206 if (diff == 0) { 207 diff = compareTo(this.branchQualifier, that.branchQualifier); 208 if (diff == 0) { 209 diff = compareTo(this.globalTransactionId, that.globalTransactionId); 210 } 211 } 212 return diff; 213 } 214 else { 215 return -1; 216 } 217 } 218 } 219 220 public String toLocalTransactionId() { 221 StringBuffer rc = new StringBuffer(13 + globalTransactionId.length * 2 + branchQualifier.length * 2); 222 rc.append(formatId); 223 rc.append(":"); 224 rc.append(toHexFromBytes(globalTransactionId)); 225 rc.append(":"); 226 rc.append(toHexFromBytes(branchQualifier)); 227 return rc.toString(); 228 } 229 230 /** 231 * @see javax.transaction.xa.Xid#getBranchQualifier() 232 */ 233 public byte[] getBranchQualifier() { 234 return branchQualifier; 235 } 236 237 /** 238 * @see javax.transaction.xa.Xid#getFormatId() 239 */ 240 public int getFormatId() { 241 return formatId; 242 } 243 244 /** 245 * @see javax.transaction.xa.Xid#getGlobalTransactionId() 246 */ 247 public byte[] getGlobalTransactionId() { 248 return globalTransactionId; 249 } 250 251 /** 252 * @see java.lang.Object#toString() 253 */ 254 public String toString() { 255 return "XID:" + toLocalTransactionId(); 256 } 257 258 259 public void writeExternal(ObjectOutput out) throws IOException { 260 write(out); 261 } 262 263 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 264 readState(in); 265 } 266 267 public void readState(DataInput dataIn) throws IOException { 268 formatId = dataIn.readInt(); 269 branchQualifier = readBytes(dataIn); 270 globalTransactionId = readBytes(dataIn); 271 } 272 273 /** 274 * Reads the Xid from a stream 275 * 276 * @param dataIn 277 * @return 278 */ 279 public static ActiveMQXid read(DataInput dataIn) throws IOException { 280 ActiveMQXid answer = new ActiveMQXid(); 281 answer.readState(dataIn); 282 return answer; 283 } 284 285 public byte[] toBytes() throws IOException { 286 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 287 write(new DataOutputStream(buffer)); 288 return buffer.toByteArray(); 289 } 290 291 /** 292 * Writes the Xid to a stream 293 * 294 * @param dataOut 295 */ 296 public void write(DataOutput dataOut) throws IOException { 297 dataOut.writeInt(formatId); 298 writeBytes(dataOut, branchQualifier); 299 writeBytes(dataOut, globalTransactionId); 300 } 301 302 protected void writeBytes(DataOutput dataOut, byte[] data) throws IOException { 303 dataOut.writeInt(data.length); 304 dataOut.write(data); 305 } 306 307 protected static byte[] readBytes(DataInput dataIn) throws IOException { 308 int size = dataIn.readInt(); 309 byte[] data = new byte[size]; 310 dataIn.readFully(data); 311 return data; 312 } 313 314 315 public static boolean equals(byte[] left, byte[] right) { 316 if (left == right) { 317 return true; 318 } 319 int size = left.length; 320 if (size != right.length) { 321 return false; 322 } 323 for (int i = 0; i < size; i++) { 324 if (left[i] != right[i]) { 325 return false; 326 } 327 } 328 return true; 329 } 330 331 protected int compareTo(byte[] left, byte[] right) { 332 if (left == right) { 333 return 0; 334 } 335 int size = left.length; 336 int answer = size - right.length; 337 if (answer == 0) { 338 for (int i = 0; i < size; i++) { 339 answer = left[i] - right[i]; 340 if (answer != 0) { 341 break; 342 } 343 } 344 } 345 return answer; 346 } 347 348 protected int hash(byte[] bytes, int hash) { 349 for (int i = 0, size = bytes.length; i < size; i++) { 350 hash ^= bytes[i] << ((i % 4) * 8); 351 } 352 return hash; 353 } 354 355 /** 356 * @param hex 357 * @return 358 */ 359 public static byte[] toBytesFromHex(String hex) { 360 byte rc[] = new byte[hex.length() / 2]; 361 for (int i = 0; i < rc.length; i++) { 362 String h = hex.substring(i * 2, i * 2 + 2); 363 int x = Integer.parseInt(h, 16); 364 rc[i] = (byte) x; 365 } 366 return rc; 367 } 368 369 /** 370 * @param bytes 371 * @return 372 */ 373 public static String toHexFromBytes(byte[] bytes) { 374 StringBuffer rc = new StringBuffer(bytes.length * 2); 375 for (int i = 0; i < bytes.length; i++) { 376 rc.append(HEX_TABLE[0xFF & bytes[i]]); 377 } 378 return rc.toString(); 379 } 380 }