EMMA Coverage Report (generated Mon Jul 24 20:33:06 CDT 2006)
[all classes][com.mysql.jdbc.jdbc2.optional]

COVERAGE SUMMARY FOR SOURCE FILE [MysqlXAConnection.java]

nameclass, %method, %block, %line, %
MysqlXAConnection.java100% (1/1)78%  (14/18)83%  (530/640)82%  (128.4/156)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class MysqlXAConnection100% (1/1)78%  (14/18)83%  (530/640)82%  (128.4/156)
getTransactionTimeout (): int 0%   (0/1)0%   (0/2)0%   (0/1)
isSameRM (XAResource): boolean 0%   (0/1)0%   (0/12)0%   (0/3)
mapXAExceptionFromSQLException (SQLException): XAException 0%   (0/1)0%   (0/27)0%   (0/4)
setTransactionTimeout (int): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
end (Xid, int): void 100% (1/1)67%  (22/33)64%  (7/11)
start (Xid, int): void 100% (1/1)76%  (31/41)77%  (10/13)
rollback (Xid): void 100% (1/1)77%  (24/31)97%  (6.8/7)
commit (Xid, boolean): void 100% (1/1)81%  (30/37)95%  (8.6/9)
dispatchCommand (String): ResultSet 100% (1/1)82%  (40/49)73%  (9.4/13)
recover (Connection, int): Xid [] 100% (1/1)86%  (141/164)82%  (34.6/42)
<static initializer> 100% (1/1)100% (74/74)100% (9/9)
MysqlXAConnection (Connection): void 100% (1/1)100% (11/11)100% (4/4)
forget (Xid): void 100% (1/1)100% (1/1)100% (1/1)
getConnection (): Connection 100% (1/1)100% (7/7)100% (2/2)
getXAResource (): XAResource 100% (1/1)100% (2/2)100% (1/1)
prepare (Xid): int 100% (1/1)100% (20/20)100% (5/5)
recover (int): Xid [] 100% (1/1)100% (5/5)100% (1/1)
xidToString (Xid): String 100% (1/1)100% (122/122)100% (29/29)

1/*
2 Copyright (C) 2005 MySQL AB
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as 
6 published by the Free Software Foundation.
7 
8 There are special exceptions to the terms and conditions of the GPL 
9 as it is applied to this software. View the full text of the 
10 exception in file EXCEPTIONS-CONNECTOR-J in the directory of this 
11 software distribution.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22 */
23package com.mysql.jdbc.jdbc2.optional;
24 
25import java.sql.Connection;
26import java.sql.ResultSet;
27import java.sql.SQLException;
28import java.sql.Statement;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34 
35import javax.sql.XAConnection;
36import javax.transaction.xa.XAException;
37import javax.transaction.xa.XAResource;
38import javax.transaction.xa.Xid;
39 
40import com.mysql.jdbc.log.Log;
41 
42/*
43 * XA BEGIN <xid> [JOIN | RESUME] XA START TRANSACTION <xid> [JOIN | RESUME] XA
44 * COMMIT <xid> [ONE PHASE] XA END <xid> [SUSPEND [FOR MIGRATE]] XA PREPARE
45 * <xid> XA RECOVER XA ROLLBACK <xid>
46 */
47 
48/**
49 * An object that provides support for distributed transactions. An
50 * <code>XAConnection</code> object may be enlisted in a distributed
51 * transaction by means of an <code>XAResource</code> object. A transaction
52 * manager, usually part of a middle tier server, manages an
53 * <code>XAConnection</code> object through the <code>XAResource</code>
54 * object.
55 * <P>
56 * An application programmer does not use this interface directly; rather, it is
57 * used by a transaction manager working in the middle tier server.
58 * 
59 * @since 1.4
60 */
61public class MysqlXAConnection extends MysqlPooledConnection implements
62                XAConnection, XAResource {
63 
64        private com.mysql.jdbc.Connection underlyingConnection;
65 
66        private final static Map MYSQL_ERROR_CODES_TO_XA_ERROR_CODES;
67 
68        private Log log;
69 
70        static {
71                HashMap temp = new HashMap();
72 
73                temp.put(new Integer(1397), new Integer(XAException.XAER_NOTA));
74                temp.put(new Integer(1398), new Integer(XAException.XAER_INVAL));
75                temp.put(new Integer(1399), new Integer(XAException.XAER_RMFAIL));
76                temp.put(new Integer(1400), new Integer(XAException.XAER_OUTSIDE));
77                temp.put(new Integer(1401), new Integer(XAException.XAER_RMERR));
78                temp.put(new Integer(1402), new Integer(XAException.XA_RBROLLBACK));
79 
80                MYSQL_ERROR_CODES_TO_XA_ERROR_CODES = Collections.unmodifiableMap(temp);
81        }
82 
83        /**
84         * @param connection
85         */
86        public MysqlXAConnection(com.mysql.jdbc.Connection connection)
87                        throws SQLException {
88                super(connection);
89                this.underlyingConnection = connection;
90                this.log = connection.getLog();
91        }
92 
93        /**
94         * Retrieves an <code>XAResource</code> object that the transaction
95         * manager will use to manage this <code>XAConnection</code> object's
96         * participation in a distributed transaction.
97         * 
98         * @return the <code>XAResource</code> object
99         * @exception SQLException
100         *                if a database access error occurs
101         */
102        public XAResource getXAResource() throws SQLException {
103                return this;
104        }
105 
106        /**
107         * Obtains the current transaction timeout value set for this XAResource
108         * instance. If XAResource.setTransactionTimeout was not used prior to
109         * invoking this method, the return value is the default timeout set for the
110         * resource manager; otherwise, the value used in the previous
111         * setTransactionTimeout call is returned.
112         * 
113         * @return the transaction timeout value in seconds.
114         * 
115         * @throws XAException
116         *             An error has occurred. Possible exception values are
117         *             XAER_RMERR and XAER_RMFAIL.
118         */
119        public int getTransactionTimeout() throws XAException {
120                // TODO Auto-generated method stub
121                return 0;
122        }
123 
124        /**
125         * Sets the current transaction timeout value for this XAResource instance.
126         * Once set, this timeout value is effective until setTransactionTimeout is
127         * invoked again with a different value.
128         * 
129         * To reset the timeout value to the default value used by the resource
130         * manager, set the value to zero. If the timeout operation is performed
131         * successfully, the method returns true; otherwise false.
132         * 
133         * If a resource manager does not support explicitly setting the transaction
134         * timeout value, this method returns false.
135         * 
136         * @parameter seconds The transaction timeout value in seconds.
137         * 
138         * @return true if the transaction timeout value is set successfully;
139         *         otherwise false.
140         * 
141         * @throws XAException
142         *             An error has occurred. Possible exception values are
143         *             XAER_RMERR, XAER_RMFAIL, or XAER_INVAL.
144         */
145        public boolean setTransactionTimeout(int arg0) throws XAException {
146                // TODO Auto-generated method stub
147                return false;
148        }
149 
150        /**
151         * This method is called to determine if the resource manager instance
152         * represented by the target object is the same as the resouce manager
153         * instance represented by the parameter xares.
154         * 
155         * @parameter xares An XAResource object whose resource manager instance is
156         *            to be compared with the resource manager instance of the
157         *            target object.
158         * 
159         * @return true if it's the same RM instance; otherwise false.
160         * 
161         * @throws XAException
162         *             An error has occurred. Possible exception values are
163         *             XAER_RMERR and XAER_RMFAIL.
164         */
165        public boolean isSameRM(XAResource xares) throws XAException {
166 
167                if (xares instanceof MysqlXAConnection) {
168                        return this.underlyingConnection
169                                        .isSameResource(((MysqlXAConnection) xares).underlyingConnection);
170                }
171 
172                return false;
173        }
174 
175        /**
176         * This method is called to obtain a list of prepared transaction branches
177         * from a resource manager. The transaction manager calls this method during
178         * recovery to obtain the list of transaction branches that are currently in
179         * prepared or heuristically completed states. 
180         * 
181         * The flag parameter indicates where the recover scan should start or end, 
182         * or start and end. This method may be invoked one or more times during a 
183         * recovery scan. The resource manager maintains a cursor which marks the 
184         * current position of the prepared or heuristically completed transaction list. 
185         * Each invocation of the recover method moves the cursor passed the set of Xids 
186         * that are returned. 
187         * 
188         * Two consecutive invocation of this method that starts from the
189         * beginning of the list must return the same list of transaction branches
190         * unless one of the following takes place: 
191         * 
192         * - the transaction manager invokes the commit, forget, prepare, or rollback method for that resource
193         * manager, between the two consecutive invocation of the recovery scan. 
194         * 
195         * - the resource manager heuristically completes some transaction branches
196         * between the two invocation of the recovery scan.
197         * 
198         * @param flag
199         *            One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS must be
200         *            used when no other flags are set in the parameter.
201         * 
202         * @returns The resource manager returns zero or more XIDs of the
203         *          transaction branches that are currently in a prepared or
204         *          heuristically completed state. If an error occurs during the
205         *          operation, the resource manager should throw the appropriate
206         *          XAException.
207         * 
208         * @throws XAException
209         *             An error has occurred. Possible values are XAER_RMERR,
210         *             XAER_RMFAIL, XAER_INVAL, and XAER_PROTO.
211         */
212        public Xid[] recover(int flag) throws XAException {
213                return recover(this.underlyingConnection, flag);
214        }
215        
216        protected static Xid[] recover(Connection c, int flag) throws XAException {
217                /*
218                    The XA RECOVER statement returns information for those XA transactions on the MySQL server that are in the PREPARED state. (See Section 13.4.7.2, ?XA Transaction States?.) The output includes a row for each such XA transaction on the server, regardless of which client started it.
219 
220                        XA RECOVER output rows look like this (for an example xid value consisting of the parts 'abc', 'def', and 7):
221 
222                        mysql> XA RECOVER;
223                        +----------+--------------+--------------+--------+
224                        | formatID | gtrid_length | bqual_length | data   |
225                        +----------+--------------+--------------+--------+
226                        |        7 |            3 |            3 | abcdef |
227                        +----------+--------------+--------------+--------+
228 
229                        The output columns have the following meanings:
230 
231                              formatID is the formatID part of the transaction xid
232                            gtrid_length is the length in bytes of the gtrid part of the xid
233                            bqual_length is the length in bytes of the bqual part of the xid
234                             data is the concatenation of the gtrid and bqual parts of the xid
235                 */
236                
237                boolean startRscan = ((flag & TMSTARTRSCAN) > 0);
238                boolean endRscan = ((flag & TMENDRSCAN) > 0);
239                
240                if (!startRscan && !endRscan && flag != TMNOFLAGS) {
241                        throw new MysqlXAException(XAException.XAER_INVAL, 
242                                        "Invalid flag, must use TMNOFLAGS, or any combination of TMSTARTRSCAN and TMENDRSCAN",
243                                        null);
244                }
245 
246                //
247                // We return all recovered XIDs at once, so if not 
248                // TMSTARTRSCAN, return no new XIDs
249                //
250                // We don't attempt to maintain state to check for TMNOFLAGS
251                // "outside" of a scan
252                //
253                
254                if (!startRscan) {
255                        return new Xid[0];
256                }
257                
258                ResultSet rs = null;
259                Statement stmt = null;
260 
261                List recoveredXidList = new ArrayList();
262 
263                try {
264                        // TODO: Cache this for lifetime of XAConnection
265                        stmt = c.createStatement();
266 
267                        rs = stmt.executeQuery("XA RECOVER");
268 
269                        while (rs.next()) {
270                                final int formatId = rs.getInt(1);
271                                int gtridLength = rs.getInt(2);
272                                int bqualLength = rs.getInt(3);
273                                byte[] gtridAndBqual = rs.getBytes(4);
274 
275                                final byte[] gtrid = new byte[gtridLength];
276                                final byte[] bqual = new byte[bqualLength];
277 
278                                if (gtridAndBqual.length != (gtridLength + bqualLength)) {
279                                        throw new MysqlXAException(XAException.XA_RBPROTO,
280                                                        "Error while recovering XIDs from RM. GTRID and BQUAL are wrong sizes", 
281                                                        null);
282                                }
283 
284                                System.arraycopy(gtridAndBqual, 0, gtrid, 0,
285                                                gtridLength);
286                                System.arraycopy(gtridAndBqual, gtridLength, bqual, 0,
287                                                bqualLength);
288 
289                                recoveredXidList.add(new MysqlXid(gtrid, bqual, 
290                                                formatId));
291                        }
292                } catch (SQLException sqlEx) {
293                        throw mapXAExceptionFromSQLException(sqlEx);
294                } finally {
295                        if (rs != null) {
296                                try {
297                                        rs.close();
298                                } catch (SQLException sqlEx) {
299                                        throw mapXAExceptionFromSQLException(sqlEx);
300                                }
301                        }
302 
303                        if (stmt != null) {
304                                try {
305                                        stmt.close();
306                                } catch (SQLException sqlEx) {
307                                        throw mapXAExceptionFromSQLException(sqlEx);
308                                }
309                        }
310                }
311 
312                int numXids = recoveredXidList.size();
313 
314                Xid[] asXids = new Xid[numXids];
315                Object[] asObjects = recoveredXidList.toArray();
316 
317                for (int i = 0; i < numXids; i++) {
318                        asXids[i] = (Xid) asObjects[i];
319                }
320 
321                return asXids;
322        }
323 
324        /**
325         * Asks the resource manager to prepare for a transaction commit of the
326         * transaction specified in xid.
327         * 
328         * @parameter xid A global transaction identifier.
329         * 
330         * @returns A value indicating the resource manager's vote on the outcome of
331         *          the transaction.
332         * 
333         * The possible values are: XA_RDONLY or XA_OK. If the resource manager
334         * wants to roll back the transaction, it should do so by raising an
335         * appropriate XAException in the prepare method.
336         * 
337         * @throws XAException
338         *             An error has occurred. Possible exception values are: XA_RB*,
339         *             XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
340         *             XAER_PROTO.
341         */
342        public int prepare(Xid xid) throws XAException {
343                StringBuffer commandBuf = new StringBuffer();
344                commandBuf.append("XA PREPARE ");
345                commandBuf.append(xidToString(xid));
346 
347                dispatchCommand(commandBuf.toString());
348 
349                return XA_OK; // TODO: Check for read-only
350        }
351 
352        /**
353         * Tells the resource manager to forget about a heuristically completed
354         * transaction branch.
355         * 
356         * @parameter xid A global transaction identifier.
357         * 
358         * @throws XAException
359         *             An error has occurred. Possible exception values are
360         *             XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
361         *             XAER_PROTO.
362         */
363        public void forget(Xid xid) throws XAException {
364                // TODO Auto-generated method stub
365        }
366 
367        /**
368         * Informs the resource manager to roll back work done on behalf of a
369         * transaction branch.
370         * 
371         * @parameter xid A global transaction identifier.
372         * 
373         * @throws XAException
374         *             An error has occurred. Possible XAExceptions are XA_HEURHAZ,
375         *             XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL,
376         *             XAER_NOTA, XAER_INVAL, or XAER_PROTO.
377         * 
378         * If the transaction branch is already marked rollback-only the resource
379         * manager may throw one of the XA_RB* exceptions.
380         * 
381         * Upon return, the resource manager has rolled back the branch's work and
382         * has released all held resources.
383         */
384        public void rollback(Xid xid) throws XAException {
385                StringBuffer commandBuf = new StringBuffer();
386                commandBuf.append("XA ROLLBACK ");
387                commandBuf.append(xidToString(xid));
388 
389                try {
390                        dispatchCommand(commandBuf.toString());
391                } finally {
392                        this.underlyingConnection.setInGlobalTx(false);
393                }
394        }
395 
396        /**
397         * Ends the work performed on behalf of a transaction branch.
398         * 
399         * The resource manager disassociates the XA resource from the transaction
400         * branch specified and lets the transaction complete.
401         * 
402         * If TMSUSPEND is specified in the flags, the transaction branch is
403         * temporarily suspended in an incomplete state. The transaction context is
404         * in a suspended state and must be resumed via the start method with
405         * TMRESUME specified.
406         * 
407         * If TMFAIL is specified, the portion of work has failed. The resource
408         * manager may mark the transaction as rollback-only
409         * 
410         * If TMSUCCESS is specified, the portion of work has completed
411         * successfully.
412         * 
413         * @parameter xid A global transaction identifier that is the same as the
414         *            identifier used previously in the start method.
415         * 
416         * @parameter flags One of TMSUCCESS, TMFAIL, or TMSUSPEND.
417         * 
418         * @throws XAException -
419         *             An error has occurred. Possible XAException values are
420         *             XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, XAER_PROTO,
421         *             or XA_RB*.
422         */
423        public void end(Xid xid, int flags) throws XAException {
424                StringBuffer commandBuf = new StringBuffer();
425                commandBuf.append("XA END ");
426                commandBuf.append(xidToString(xid));
427 
428                switch (flags) {
429                case TMSUCCESS:
430                        break; // no-op
431                case TMSUSPEND:
432                        commandBuf.append(" SUSPEND");
433                        break;
434                case TMFAIL:
435                        break; // no-op
436                default:
437                        throw new XAException(XAException.XAER_INVAL);
438                }
439 
440                dispatchCommand(commandBuf.toString());
441        }
442 
443        /**
444         * Starts work on behalf of a transaction branch specified in xid.
445         * 
446         * If TMJOIN is specified, the start applies to joining a transaction
447         * previously seen by the resource manager.
448         * 
449         * If TMRESUME is specified, the start applies to resuming a suspended
450         * transaction specified in the parameter xid.
451         * 
452         * If neither TMJOIN nor TMRESUME is specified and the transaction specified
453         * by xid has previously been seen by the resource manager, the resource
454         * manager throws the XAException exception with XAER_DUPID error code.
455         * 
456         * @parameter xid A global transaction identifier to be associated with the
457         *            resource.
458         * 
459         * @parameter flags One of TMNOFLAGS, TMJOIN, or TMRESUME.
460         * 
461         * @throws XAException
462         *             An error has occurred. Possible exceptions are XA_RB*,
463         *             XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE, XAER_NOTA,
464         *             XAER_INVAL, or XAER_PROTO.
465         */
466        public void start(Xid xid, int flags) throws XAException {
467                StringBuffer commandBuf = new StringBuffer();
468                commandBuf.append("XA START ");
469                commandBuf.append(xidToString(xid));
470 
471                switch (flags) {
472                case TMJOIN:
473                        commandBuf.append(" JOIN");
474                        break;
475                case TMRESUME:
476                        commandBuf.append(" RESUME");
477                        break;
478                case TMNOFLAGS:
479                        // no-op
480                        break;
481                default:
482                        throw new XAException(XAException.XAER_INVAL);
483                }
484 
485                dispatchCommand(commandBuf.toString());
486                
487                this.underlyingConnection.setInGlobalTx(true);
488        }
489 
490        /**
491         * Commits the global transaction specified by xid.
492         * 
493         * @parameter xid A global transaction identifier
494         * @parameter onePhase - If true, the resource manager should use a
495         *            one-phase commit protocol to commit the work done on behalf of
496         *            xid.
497         * 
498         * @throws XAException
499         *             An error has occurred. Possible XAExceptions are XA_HEURHAZ,
500         *             XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL,
501         *             XAER_NOTA, XAER_INVAL, or XAER_PROTO.
502         * 
503         * If the resource manager did not commit the transaction and the parameter
504         * onePhase is set to true, the resource manager may throw one of the XA_RB*
505         * exceptions.
506         * 
507         * Upon return, the resource manager has rolled back the branch's work and
508         * has released all held resources.
509         */
510 
511        public void commit(Xid xid, boolean onePhase) throws XAException {
512                StringBuffer commandBuf = new StringBuffer();
513                commandBuf.append("XA COMMIT ");
514                commandBuf.append(xidToString(xid));
515 
516                if (onePhase) {
517                        commandBuf.append(" ONE PHASE");
518                }
519 
520                try {
521                        dispatchCommand(commandBuf.toString());
522                } finally {
523                        this.underlyingConnection.setInGlobalTx(false);
524                }
525        }
526 
527        private ResultSet dispatchCommand(String command) throws XAException {
528                Statement stmt = null;
529 
530                try {
531                        if (this.log.isDebugEnabled()) {
532                                this.log.logDebug("Executing XA statement: " + command);
533                        }
534 
535                        // TODO: Cache this for lifetime of XAConnection
536                        stmt = this.underlyingConnection.createStatement();
537                        
538                        
539                        stmt.execute(command);
540 
541                        ResultSet rs = stmt.getResultSet();
542                        
543                        return rs;
544                } catch (SQLException sqlEx) {
545                        throw mapXAExceptionFromSQLException(sqlEx);
546                } finally {
547                        if (stmt != null) {
548                                try {
549                                        stmt.close();
550                                } catch (SQLException sqlEx) {
551                                }
552                        }
553                }
554        }
555 
556        protected static XAException mapXAExceptionFromSQLException(SQLException sqlEx) {
557 
558                Integer xaCode = (Integer) MYSQL_ERROR_CODES_TO_XA_ERROR_CODES
559                                .get(new Integer(sqlEx.getErrorCode()));
560 
561                if (xaCode != null) {
562                        return new MysqlXAException(xaCode.intValue(), sqlEx.getMessage(), null);
563                }
564 
565                // Punt? We don't know what the error code is here
566                return new MysqlXAException(sqlEx.getMessage(), null);
567        }
568 
569        private static String xidToString(Xid xid) {
570                byte[] gtrid = xid.getGlobalTransactionId();
571 
572                byte[] btrid = xid.getBranchQualifier();
573 
574                int lengthAsString = 6; // for (0x and ,) * 2
575 
576                if (gtrid != null) {
577                        lengthAsString += (2 * gtrid.length);
578                }
579 
580                if (btrid != null) {
581                        lengthAsString += (2 * btrid.length);
582                }
583 
584                String formatIdInHex = Integer.toHexString(xid.getFormatId());
585 
586                lengthAsString += formatIdInHex.length();
587                lengthAsString += 3; // for the '.' after formatId
588 
589                StringBuffer asString = new StringBuffer(lengthAsString);
590 
591                asString.append("0x");
592 
593                if (gtrid != null) {
594                        for (int i = 0; i < gtrid.length; i++) {
595                                String asHex = Integer.toHexString(gtrid[i] & 0xff);
596 
597                                if (asHex.length() == 1) {
598                                        asString.append("0");
599                                }
600 
601                                asString.append(asHex);
602                        }
603                }
604 
605                asString.append(",");
606 
607                if (btrid != null) {
608                        asString.append("0x");
609 
610                        for (int i = 0; i < btrid.length; i++) {
611                                String asHex = Integer.toHexString(btrid[i] & 0xff);
612 
613                                if (asHex.length() == 1) {
614                                        asString.append("0");
615                                }
616 
617                                asString.append(asHex);
618                        }
619                }
620 
621                asString.append(",0x");
622                asString.append(formatIdInHex);
623 
624                return asString.toString();
625        }
626 
627        /*
628         * (non-Javadoc)
629         * 
630         * @see javax.sql.PooledConnection#getConnection()
631         */
632        public synchronized Connection getConnection() throws SQLException {
633                Connection connToWrap = getConnection(false, true);
634                
635                return connToWrap;
636        }
637}

[all classes][com.mysql.jdbc.jdbc2.optional]
EMMA 2.0.4217 (C) Vladimir Roubtsov