001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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    package org.apache.activemq.command;
018    
019    import java.io.IOException;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import javax.transaction.xa.Xid;
023    import org.apache.activemq.util.DataByteArrayInputStream;
024    import org.apache.activemq.util.DataByteArrayOutputStream;
025    
026    /**
027     * @openwire:marshaller code="112"
028     * 
029     */
030    public class XATransactionId extends TransactionId implements Xid, Comparable {
031    
032        public static final byte DATA_STRUCTURE_TYPE = CommandTypes.ACTIVEMQ_XA_TRANSACTION_ID;
033    
034        private int formatId;
035        private byte[] branchQualifier;
036        private byte[] globalTransactionId;
037        private transient DataByteArrayOutputStream outputStream;
038        private transient byte[] encodedXidBytes;
039    
040        private transient int hash;
041        private transient String transactionKey;
042        private transient ArrayList<MessageAck> preparedAcks;
043    
044        public XATransactionId() {
045        }
046    
047        public XATransactionId(Xid xid) {
048            this.formatId = xid.getFormatId();
049            this.globalTransactionId = xid.getGlobalTransactionId();
050            this.branchQualifier = xid.getBranchQualifier();
051        }
052    
053        public XATransactionId(byte[] encodedBytes) {
054            encodedXidBytes = encodedBytes;
055            initFromEncodedBytes();
056        }
057    
058        public byte getDataStructureType() {
059            return DATA_STRUCTURE_TYPE;
060        }
061    
062        final int XID_PREFIX_SIZE = 16;
063        //+|-,(long)lastAck,(byte)priority,(int)formatid,(short)globalLength....
064        private void initFromEncodedBytes() {
065            DataByteArrayInputStream inputStream = new DataByteArrayInputStream(encodedXidBytes);
066            inputStream.skipBytes(10);
067            formatId = inputStream.readInt();
068            int globalLength = inputStream.readShort();
069            globalTransactionId = new byte[globalLength];
070            try {
071                inputStream.read(globalTransactionId);
072                branchQualifier = new byte[inputStream.available()];
073                inputStream.read(branchQualifier);
074            } catch (IOException fatal) {
075                throw new RuntimeException(this + ", failed to decode:", fatal);
076            }
077        }
078    
079        public synchronized byte[] getEncodedXidBytes() {
080            if (encodedXidBytes == null) {
081                outputStream = new DataByteArrayOutputStream(XID_PREFIX_SIZE + globalTransactionId.length + branchQualifier.length);
082                outputStream.position(10);
083                outputStream.writeInt(formatId);
084                // global length
085                outputStream.writeShort(globalTransactionId.length);
086                try {
087                    outputStream.write(globalTransactionId);
088                    outputStream.write(branchQualifier);
089                } catch (IOException fatal) {
090                    throw new RuntimeException(this + ", failed to encode:", fatal);
091                }
092                encodedXidBytes = outputStream.getData();
093            }
094            return encodedXidBytes;
095        }
096    
097        public DataByteArrayOutputStream internalOutputStream() {
098            return outputStream;
099        }
100    
101        public synchronized String getTransactionKey() {
102            if (transactionKey == null) {
103                StringBuffer s = new StringBuffer();
104                s.append("XID:[" + formatId + ",globalId=");
105                for (int i = 0; i < globalTransactionId.length; i++) {
106                    s.append(Integer.toHexString(globalTransactionId[i]));
107                }
108                s.append(",branchId=");
109                for (int i = 0; i < branchQualifier.length; i++) {
110                    s.append(Integer.toHexString(branchQualifier[i]));
111                }
112                s.append("]");
113                transactionKey = s.toString();
114            }
115            return transactionKey;
116        }
117    
118        public String toString() {
119            return getTransactionKey();
120        }
121    
122        public boolean isXATransaction() {
123            return true;
124        }
125    
126        public boolean isLocalTransaction() {
127            return false;
128        }
129    
130        /**
131         * @openwire:property version=1
132         */
133        public int getFormatId() {
134            return formatId;
135        }
136    
137        /**
138         * @openwire:property version=1
139         */
140        public byte[] getGlobalTransactionId() {
141            return globalTransactionId;
142        }
143    
144        /**
145         * @openwire:property version=1
146         */
147        public byte[] getBranchQualifier() {
148            return branchQualifier;
149        }
150    
151        public void setBranchQualifier(byte[] branchQualifier) {
152            this.branchQualifier = branchQualifier;
153            this.hash = 0;
154        }
155    
156        public void setFormatId(int formatId) {
157            this.formatId = formatId;
158            this.hash = 0;
159        }
160    
161        public void setGlobalTransactionId(byte[] globalTransactionId) {
162            this.globalTransactionId = globalTransactionId;
163            this.hash = 0;
164        }
165    
166        public int hashCode() {
167            if (hash == 0) {
168                hash = formatId;
169                hash = hash(globalTransactionId, hash);
170                hash = hash(branchQualifier, hash);
171                if (hash == 0) {
172                    hash = 0xaceace;
173                }
174            }
175            return hash;
176        }
177    
178        private static int hash(byte[] bytes, int hash) {
179            int size = bytes.length;
180            for (int i = 0; i < size; i++) {
181                hash ^= bytes[i] << ((i % 4) * 8);
182            }
183            return hash;
184        }
185    
186        public boolean equals(Object o) {
187            if (o == null || o.getClass() != XATransactionId.class) {
188                return false;
189            }
190            XATransactionId xid = (XATransactionId)o;
191            return xid.formatId == formatId && Arrays.equals(xid.globalTransactionId, globalTransactionId)
192                   && Arrays.equals(xid.branchQualifier, branchQualifier);
193        }
194    
195        public int compareTo(Object o) {
196            if (o == null || o.getClass() != XATransactionId.class) {
197                return -1;
198            }
199            XATransactionId xid = (XATransactionId)o;
200            return getTransactionKey().compareTo(xid.getTransactionKey());
201        }
202    
203        public void setPreparedAcks(ArrayList<MessageAck> preparedAcks) {
204            this.preparedAcks = preparedAcks;
205        }
206    
207        public ArrayList<MessageAck> getPreparedAcks() {
208            return preparedAcks;
209        }
210    }