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 }