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    }