EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.jdbcx]

COVERAGE SUMMARY FOR SOURCE FILE [JdbcXAConnection.java]

nameclass, %method, %block, %line, %
JdbcXAConnection.java100% (2/2)86%  (24/28)79%  (574/724)80%  (150.7/189)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class JdbcXAConnection100% (1/1)83%  (20/24)79%  (538/680)79%  (137.1/173)
addStatementEventListener (StatementEventListener): void 0%   (0/1)0%   (0/4)0%   (0/1)
convertException (SQLException): XAException 0%   (0/1)0%   (0/12)0%   (0/3)
forget (Xid): void 0%   (0/1)0%   (0/20)0%   (0/4)
removeStatementEventListener (StatementEventListener): void 0%   (0/1)0%   (0/4)0%   (0/1)
checkOpen (): void 100% (1/1)44%  (4/9)67%  (2/3)
start (Xid, int): void 100% (1/1)69%  (45/65)65%  (10.4/16)
quoteFlags (int): String 100% (1/1)77%  (68/88)77%  (17/22)
prepare (Xid): int 100% (1/1)78%  (49/63)77%  (10.8/14)
recover (int): Xid [] 100% (1/1)79%  (64/81)81%  (18.5/23)
end (Xid, int): void 100% (1/1)85%  (33/39)75%  (6/8)
rollback (Xid): void 100% (1/1)86%  (55/64)86%  (13.8/16)
commit (Xid, boolean): void 100% (1/1)87%  (58/67)84%  (12.6/15)
getConnection (): Connection 100% (1/1)92%  (22/24)86%  (6/7)
<static initializer> 100% (1/1)100% (3/3)100% (2/2)
JdbcXAConnection (JdbcDataSourceFactory, int, JdbcConnection): void 100% (1/1)100% (18/18)100% (6/6)
addConnectionEventListener (ConnectionEventListener): void 100% (1/1)100% (9/9)100% (3/3)
close (): void 100% (1/1)100% (30/30)100% (10/10)
closedHandle (): void 100% (1/1)100% (31/31)100% (7/7)
getTransactionTimeout (): int 100% (1/1)100% (5/5)100% (2/2)
getXAResource (): XAResource 100% (1/1)100% (5/5)100% (2/2)
isSameRM (XAResource): boolean 100% (1/1)100% (10/10)100% (2/2)
removeConnectionEventListener (ConnectionEventListener): void 100% (1/1)100% (9/9)100% (3/3)
setTransactionTimeout (int): boolean 100% (1/1)100% (7/7)100% (2/2)
toString (): String 100% (1/1)100% (13/13)100% (1/1)
     
class JdbcXAConnection$PooledJdbcConnection100% (1/1)100% (4/4)82%  (36/44)85%  (13.6/16)
isClosed (): boolean 100% (1/1)60%  (6/10)60%  (0.6/1)
checkClosed (boolean): void 100% (1/1)70%  (7/10)75%  (3/4)
close (): void 100% (1/1)94%  (16/17)88%  (7/8)
JdbcXAConnection$PooledJdbcConnection (JdbcXAConnection, JdbcConnection): void 100% (1/1)100% (7/7)100% (3/3)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.jdbcx;
7 
8import java.sql.Connection;
9import java.sql.ResultSet;
10import java.sql.SQLException;
11import java.sql.Statement;
12import java.util.ArrayList;
13import javax.sql.ConnectionEvent;
14import javax.sql.ConnectionEventListener;
15import javax.sql.StatementEventListener;
16import javax.sql.XAConnection;
17import javax.transaction.xa.XAException;
18import javax.transaction.xa.XAResource;
19import javax.transaction.xa.Xid;
20 
21import org.h2.api.ErrorCode;
22import org.h2.jdbc.JdbcConnection;
23import org.h2.util.JdbcUtils;
24import org.h2.util.New;
25 
26import org.h2.message.DbException;
27import org.h2.message.TraceObject;
28 
29 
30/**
31 * This class provides support for distributed transactions.
32 * An application developer usually does not use this interface.
33 * It is used by the transaction manager internally.
34 */
35public class JdbcXAConnection extends TraceObject implements XAConnection,
36        XAResource {
37 
38    private final JdbcDataSourceFactory factory;
39 
40    // this connection is kept open as long as the XAConnection is alive
41    private JdbcConnection physicalConn;
42 
43    // this connection is replaced whenever getConnection is called
44    private volatile Connection handleConn;
45    private final ArrayList<ConnectionEventListener> listeners = New.arrayList();
46    private Xid currentTransaction;
47    private boolean prepared;
48 
49    static {
50        org.h2.Driver.load();
51    }
52 
53    JdbcXAConnection(JdbcDataSourceFactory factory, int id,
54            JdbcConnection physicalConn) {
55        this.factory = factory;
56        setTrace(factory.getTrace(), TraceObject.XA_DATA_SOURCE, id);
57        this.physicalConn = physicalConn;
58    }
59 
60    /**
61     * Get the XAResource object.
62     *
63     * @return itself
64     */
65    @Override
66    public XAResource getXAResource() {
67        debugCodeCall("getXAResource");
68        return this;
69    }
70 
71    /**
72     * Close the physical connection.
73     * This method is usually called by the connection pool.
74     */
75    @Override
76    public void close() throws SQLException {
77        debugCodeCall("close");
78        Connection lastHandle = handleConn;
79        if (lastHandle != null) {
80            listeners.clear();
81            lastHandle.close();
82        }
83        if (physicalConn != null) {
84            try {
85                physicalConn.close();
86            } finally {
87                physicalConn = null;
88            }
89        }
90    }
91 
92    /**
93     * Get a connection that is a handle to the physical connection. This method
94     * is usually called by the connection pool. This method closes the last
95     * connection handle if one exists.
96     *
97     * @return the connection
98     */
99    @Override
100    public Connection getConnection() throws SQLException {
101        debugCodeCall("getConnection");
102        Connection lastHandle = handleConn;
103        if (lastHandle != null) {
104            lastHandle.close();
105        }
106        // this will ensure the rollback command is cached
107        physicalConn.rollback();
108        handleConn = new PooledJdbcConnection(physicalConn);
109        return handleConn;
110    }
111 
112    /**
113     * Register a new listener for the connection.
114     *
115     * @param listener the event listener
116     */
117    @Override
118    public void addConnectionEventListener(ConnectionEventListener listener) {
119        debugCode("addConnectionEventListener(listener);");
120        listeners.add(listener);
121    }
122 
123    /**
124     * Remove the event listener.
125     *
126     * @param listener the event listener
127     */
128    @Override
129    public void removeConnectionEventListener(ConnectionEventListener listener) {
130        debugCode("removeConnectionEventListener(listener);");
131        listeners.remove(listener);
132    }
133 
134    /**
135     * INTERNAL
136     */
137    void closedHandle() {
138        debugCode("closedHandle();");
139        ConnectionEvent event = new ConnectionEvent(this);
140        // go backward so that a listener can remove itself
141        // (otherwise we need to clone the list)
142        for (int i = listeners.size() - 1; i >= 0; i--) {
143            ConnectionEventListener listener = listeners.get(i);
144            listener.connectionClosed(event);
145        }
146        handleConn = null;
147    }
148 
149    /**
150     * Get the transaction timeout.
151     *
152     * @return 0
153     */
154    @Override
155    public int getTransactionTimeout() {
156        debugCodeCall("getTransactionTimeout");
157        return 0;
158    }
159 
160    /**
161     * Set the transaction timeout.
162     *
163     * @param seconds ignored
164     * @return false
165     */
166    @Override
167    public boolean setTransactionTimeout(int seconds) {
168        debugCodeCall("setTransactionTimeout", seconds);
169        return false;
170    }
171 
172    /**
173     * Checks if this is the same XAResource.
174     *
175     * @param xares the other object
176     * @return true if this is the same object
177     */
178    @Override
179    public boolean isSameRM(XAResource xares) {
180        debugCode("isSameRM(xares);");
181        return xares == this;
182    }
183 
184    /**
185     * Get the list of prepared transaction branches. This method is called by
186     * the transaction manager during recovery.
187     *
188     * @param flag TMSTARTRSCAN, TMENDRSCAN, or TMNOFLAGS. If no other flags are
189     *            set, TMNOFLAGS must be used.
190     * @return zero or more Xid objects
191     */
192    @Override
193    public Xid[] recover(int flag) throws XAException {
194        debugCodeCall("recover", quoteFlags(flag));
195        checkOpen();
196        Statement stat = null;
197        try {
198            stat = physicalConn.createStatement();
199            ResultSet rs = stat.executeQuery("SELECT * FROM " +
200                    "INFORMATION_SCHEMA.IN_DOUBT ORDER BY TRANSACTION");
201            ArrayList<Xid> list = New.arrayList();
202            while (rs.next()) {
203                String tid = rs.getString("TRANSACTION");
204                int id = getNextId(XID);
205                Xid xid = new JdbcXid(factory, id, tid);
206                list.add(xid);
207            }
208            rs.close();
209            Xid[] result = new Xid[list.size()];
210            list.toArray(result);
211            if (list.size() > 0) {
212                prepared = true;
213            }
214            return result;
215        } catch (SQLException e) {
216            XAException xa = new XAException(XAException.XAER_RMERR);
217            xa.initCause(e);
218            throw xa;
219        } finally {
220            JdbcUtils.closeSilently(stat);
221        }
222    }
223 
224    /**
225     * Prepare a transaction.
226     *
227     * @param xid the transaction id
228     * @return XA_OK
229     */
230    @Override
231    public int prepare(Xid xid) throws XAException {
232        if (isDebugEnabled()) {
233            debugCode("prepare("+JdbcXid.toString(xid)+");");
234        }
235        checkOpen();
236        if (!currentTransaction.equals(xid)) {
237            throw new XAException(XAException.XAER_INVAL);
238        }
239        Statement stat = null;
240        try {
241            stat = physicalConn.createStatement();
242            stat.execute("PREPARE COMMIT " + JdbcXid.toString(xid));
243            prepared = true;
244        } catch (SQLException e) {
245            throw convertException(e);
246        } finally {
247            JdbcUtils.closeSilently(stat);
248        }
249        return XA_OK;
250    }
251 
252    /**
253     * Forget a transaction.
254     * This method does not have an effect for this database.
255     *
256     * @param xid the transaction id
257     */
258    @Override
259    public void forget(Xid xid) {
260        if (isDebugEnabled()) {
261            debugCode("forget("+JdbcXid.toString(xid)+");");
262        }
263        prepared = false;
264    }
265 
266    /**
267     * Roll back a transaction.
268     *
269     * @param xid the transaction id
270     */
271    @Override
272    public void rollback(Xid xid) throws XAException {
273        if (isDebugEnabled()) {
274            debugCode("rollback("+JdbcXid.toString(xid)+");");
275        }
276        try {
277            physicalConn.rollback();
278            physicalConn.setAutoCommit(true);
279            if (prepared) {
280                Statement stat = null;
281                try {
282                    stat = physicalConn.createStatement();
283                    stat.execute("ROLLBACK TRANSACTION " + JdbcXid.toString(xid));
284                } finally {
285                    JdbcUtils.closeSilently(stat);
286                }
287                prepared = false;
288            }
289        } catch (SQLException e) {
290            throw convertException(e);
291        }
292        currentTransaction = null;
293    }
294 
295    /**
296     * End a transaction.
297     *
298     * @param xid the transaction id
299     * @param flags TMSUCCESS, TMFAIL, or TMSUSPEND
300     */
301    @Override
302    public void end(Xid xid, int flags) throws XAException {
303        if (isDebugEnabled()) {
304            debugCode("end("+JdbcXid.toString(xid)+", "+quoteFlags(flags)+");");
305        }
306        // TODO transaction end: implement this method
307        if (flags == TMSUSPEND) {
308            return;
309        }
310        if (!currentTransaction.equals(xid)) {
311            throw new XAException(XAException.XAER_OUTSIDE);
312        }
313        prepared = false;
314    }
315 
316    /**
317     * Start or continue to work on a transaction.
318     *
319     * @param xid the transaction id
320     * @param flags TMNOFLAGS, TMJOIN, or TMRESUME
321     */
322    @Override
323    public void start(Xid xid, int flags) throws XAException {
324        if (isDebugEnabled()) {
325            debugCode("start("+JdbcXid.toString(xid)+", "+quoteFlags(flags)+");");
326        }
327        if (flags == TMRESUME) {
328            return;
329        }
330        if (flags == TMJOIN) {
331            if (currentTransaction != null && !currentTransaction.equals(xid)) {
332                throw new XAException(XAException.XAER_RMERR);
333            }
334        } else if (currentTransaction != null) {
335            throw new XAException(XAException.XAER_NOTA);
336        }
337        try {
338            physicalConn.setAutoCommit(false);
339        } catch (SQLException e) {
340            throw convertException(e);
341        }
342        currentTransaction = xid;
343        prepared = false;
344    }
345 
346    /**
347     * Commit a transaction.
348     *
349     * @param xid the transaction id
350     * @param onePhase use a one-phase protocol if true
351     */
352    @Override
353    public void commit(Xid xid, boolean onePhase) throws XAException {
354        if (isDebugEnabled()) {
355            debugCode("commit("+JdbcXid.toString(xid)+", "+onePhase+");");
356        }
357        Statement stat = null;
358        try {
359            if (onePhase) {
360                physicalConn.commit();
361            } else {
362                stat = physicalConn.createStatement();
363                stat.execute("COMMIT TRANSACTION " + JdbcXid.toString(xid));
364                prepared = false;
365            }
366            physicalConn.setAutoCommit(true);
367        } catch (SQLException e) {
368            throw convertException(e);
369        } finally {
370            JdbcUtils.closeSilently(stat);
371        }
372        currentTransaction = null;
373    }
374 
375    /**
376     * [Not supported] Add a statement event listener.
377     *
378     * @param listener the new statement event listener
379     */
380    @Override
381    public void addStatementEventListener(StatementEventListener listener) {
382        throw new UnsupportedOperationException();
383    }
384 
385    /**
386     * [Not supported] Remove a statement event listener.
387     *
388     * @param listener the statement event listener
389     */
390    @Override
391    public void removeStatementEventListener(StatementEventListener listener) {
392        throw new UnsupportedOperationException();
393    }
394 
395    /**
396     * INTERNAL
397     */
398    @Override
399    public String toString() {
400        return getTraceObjectName() + ": " + physicalConn;
401    }
402 
403    private static XAException convertException(SQLException e) {
404        XAException xa = new XAException(e.getMessage());
405        xa.initCause(e);
406        return xa;
407    }
408 
409    private static String quoteFlags(int flags) {
410        StringBuilder buff = new StringBuilder();
411        if ((flags & XAResource.TMENDRSCAN) != 0) {
412            buff.append("|XAResource.TMENDRSCAN");
413        }
414        if ((flags & XAResource.TMFAIL) != 0) {
415            buff.append("|XAResource.TMFAIL");
416        }
417        if ((flags & XAResource.TMJOIN) != 0) {
418            buff.append("|XAResource.TMJOIN");
419        }
420        if ((flags & XAResource.TMONEPHASE) != 0) {
421            buff.append("|XAResource.TMONEPHASE");
422        }
423        if ((flags & XAResource.TMRESUME) != 0) {
424            buff.append("|XAResource.TMRESUME");
425        }
426        if ((flags & XAResource.TMSTARTRSCAN) != 0) {
427            buff.append("|XAResource.TMSTARTRSCAN");
428        }
429        if ((flags & XAResource.TMSUCCESS) != 0) {
430            buff.append("|XAResource.TMSUCCESS");
431        }
432        if ((flags & XAResource.TMSUSPEND) != 0) {
433            buff.append("|XAResource.TMSUSPEND");
434        }
435        if ((flags & XAResource.XA_RDONLY) != 0) {
436            buff.append("|XAResource.XA_RDONLY");
437        }
438        if (buff.length() == 0) {
439            buff.append("|XAResource.TMNOFLAGS");
440        }
441        return buff.toString().substring(1);
442    }
443 
444    private void checkOpen() throws XAException {
445        if (physicalConn == null) {
446            throw new XAException(XAException.XAER_RMERR);
447        }
448    }
449 
450    /**
451     * A pooled connection.
452     */
453    class PooledJdbcConnection extends JdbcConnection {
454 
455        private boolean isClosed;
456 
457        public PooledJdbcConnection(JdbcConnection conn) {
458            super(conn);
459        }
460 
461        @Override
462        public synchronized void close() throws SQLException {
463            if (!isClosed) {
464                try {
465                    rollback();
466                    setAutoCommit(true);
467                } catch (SQLException e) {
468                    // ignore
469                }
470                closedHandle();
471                isClosed = true;
472            }
473        }
474 
475        @Override
476        public synchronized boolean isClosed() throws SQLException {
477            return isClosed || super.isClosed();
478        }
479 
480        @Override
481        protected synchronized void checkClosed(boolean write) {
482            if (isClosed) {
483                throw DbException.get(ErrorCode.OBJECT_CLOSED);
484            }
485            super.checkClosed(write);
486        }
487 
488    }
489 
490}

[all classes][org.h2.jdbcx]
EMMA 2.0.5312 (C) Vladimir Roubtsov