001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019
020package org.nuxeo.ecm.core.storage.sql.ra;
021
022import java.io.PrintWriter;
023import java.util.HashSet;
024import java.util.Set;
025
026import javax.resource.ResourceException;
027import javax.resource.cci.Connection;
028import javax.resource.cci.ConnectionFactory;
029import javax.resource.spi.ConnectionEvent;
030import javax.resource.spi.ConnectionEventListener;
031import javax.resource.spi.ConnectionRequestInfo;
032import javax.resource.spi.LocalTransaction;
033import javax.resource.spi.ManagedConnection;
034import javax.resource.spi.ManagedConnectionFactory;
035import javax.resource.spi.ManagedConnectionMetaData;
036import javax.security.auth.Subject;
037import javax.transaction.xa.XAResource;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.nuxeo.common.collections.ListenerList;
042import org.nuxeo.ecm.core.storage.sql.SessionImpl;
043
044/**
045 * The managed connection represents an actual physical connection to the underlying storage. It is created by the
046 * {@link ManagedConnectionFactory}, and then encapsulated into a {@link Connection} which is then returned to the
047 * application (via the {@link ConnectionFactory}).
048 * <p>
049 * If sharing is allowed, several different {@link Connection}s may be associated to a given {@link ManagedConnection},
050 * although not at the same time.
051 *
052 * @author Florent Guillaume
053 */
054public class ManagedConnectionImpl implements ManagedConnection, ManagedConnectionMetaData {
055
056    private static final Log log = LogFactory.getLog(ManagedConnectionImpl.class);
057
058    private PrintWriter out;
059
060    private final ManagedConnectionFactoryImpl managedConnectionFactory;
061
062    /**
063     * All the {@link Connection} handles for this managed connection. There is usually only one, unless sharing is in
064     * effect.
065     */
066    private final Set<ConnectionImpl> connections;
067
068    /**
069     * The low-level session managed by this connection.
070     */
071    private final SessionImpl session;
072
073    /**
074     * The wrapped session as a connection-aware xaresource.
075     */
076    private final XAResource xaresource;
077
078    /**
079     * List of listeners set by the application server which we must notify of all activity happening on our
080     * {@link Connection}.
081     */
082    private final ListenerList listeners;
083
084    /**
085     * Creates a new physical connection to the underlying storage. Called by the {@link ManagedConnectionFactory} when
086     * it needs a new connection.
087     *
088     * @throws ResourceException
089     */
090    public ManagedConnectionImpl(ManagedConnectionFactoryImpl managedConnectionFactory) throws ResourceException {
091        log.debug("construct: " + this);
092        out = managedConnectionFactory.getLogWriter();
093        this.managedConnectionFactory = managedConnectionFactory;
094        connections = new HashSet<ConnectionImpl>();
095        listeners = new ListenerList();
096        // create the underlying session
097        session = managedConnectionFactory.getConnection();
098        xaresource = session.getXAResource();
099    }
100
101    /*
102     * ----- javax.resource.spi.ManagedConnection -----
103     */
104
105    /**
106     * Creates a new {@link Connection} handle to this {@link ManagedConnection} .
107     */
108    @Override
109    public synchronized Connection getConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo)
110            throws ResourceException {
111        // connectionRequestInfo unused
112        log.debug("getConnection: " + this);
113        ConnectionImpl connection = new ConnectionImpl(this);
114        addConnection(connection);
115        return connection;
116    }
117
118    /**
119     * Cleans up the physical connection, so that it may be reused.
120     * <p>
121     * Called by the application server before putting back this {@link ManagedConnection} into the application server
122     * pool.
123     * <p>
124     * Later, the application server may call {@link #getConnection} again.
125     */
126    @Override
127    public void cleanup() {
128        log.debug("cleanup: " + this);
129        synchronized (connections) {
130            // TODO session.cancel
131            connections.clear();
132        }
133    }
134
135    /**
136     * Destroys the physical connection.
137     * <p>
138     * Called by the application server before this {@link ManagedConnection} is destroyed.
139     */
140    @Override
141    public void destroy() throws ResourceException {
142        log.debug("destroy: " + this);
143        cleanup();
144        session.close();
145    }
146
147    /**
148     * Called by the application server to change the association of an application-level {@link Connection} handle with
149     * a {@link ManagedConnection} instance.
150     */
151    @Override
152    public void associateConnection(Object object) throws ResourceException {
153        ConnectionImpl connection = (ConnectionImpl) object;
154        log.debug("associateConnection: " + this + ", connection: " + connection);
155        ManagedConnectionImpl other = connection.getManagedConnection();
156        if (other != this) {
157            log.debug("associateConnection other: " + other);
158            other.removeConnection(connection);
159            addConnection(connection);
160        }
161    }
162
163    @Override
164    public XAResource getXAResource() {
165        return xaresource;
166    }
167
168    @Override
169    public LocalTransaction getLocalTransaction() {
170        throw new UnsupportedOperationException("Local transactions not supported");
171    }
172
173    /**
174     * Called by the application server to add a listener who should be notified of all relevant events on this
175     * connection.
176     */
177    @Override
178    public void addConnectionEventListener(ConnectionEventListener listener) {
179        listeners.add(listener);
180    }
181
182    /**
183     * Called by the application server to remove a listener.
184     */
185    @Override
186    public void removeConnectionEventListener(ConnectionEventListener listener) {
187        listeners.remove(listener);
188    }
189
190    @Override
191    public ManagedConnectionMetaData getMetaData() {
192        return this;
193    }
194
195    @Override
196    public void setLogWriter(PrintWriter out) {
197        this.out = out;
198    }
199
200    @Override
201    public PrintWriter getLogWriter() {
202        return out;
203    }
204
205    /*
206     * ----- javax.resource.spi.ManagedConnectionMetaData -----
207     */
208
209    @Override
210    public String getEISProductName() {
211        return "Nuxeo Core SQL Storage";
212    }
213
214    @Override
215    public String getEISProductVersion() {
216        return "1.0.0";
217    }
218
219    @Override
220    public int getMaxConnections() {
221        return Integer.MAX_VALUE; // or lower?
222    }
223
224    @Override
225    public String getUserName() throws ResourceException {
226        return null;
227    }
228
229    /*
230     * ----- Internal -----
231     */
232
233    /**
234     * Adds a connection to those using this managed connection.
235     */
236    private void addConnection(ConnectionImpl connection) {
237        log.debug("addConnection: " + connection);
238        if (connections.add(connection) == false) {
239            throw new IllegalStateException("already known connection " + connection + " in " + this);
240        }
241        connection.setManagedConnection(this);
242        connection.associate(session);
243    }
244
245    /**
246     * Removes a connection to those using this managed connection.
247     */
248    private void removeConnection(ConnectionImpl connection) {
249        log.debug("removeConnection: " + connection);
250        if (connections.remove(connection) == false) {
251            throw new IllegalStateException("unknown connection " + connection + " in " + this);
252        }
253        connection.setManagedConnection(null);
254        connection.disassociate();
255    }
256
257    /**
258     * Called by {@link ConnectionImpl#close} when the connection is closed.
259     */
260    protected void close(ConnectionImpl connection) {
261        removeConnection(connection);
262        sendClosedEvent(connection);
263    }
264
265    /**
266     * Called by {@link ManagedConnectionFactoryImpl#matchManagedConnections}.
267     */
268    protected ManagedConnectionFactoryImpl getManagedConnectionFactory() {
269        return managedConnectionFactory;
270    }
271
272    /*
273     * ----- Event management -----
274     */
275
276    private void sendClosedEvent(ConnectionImpl connection) {
277        log.debug("closing a connection " + connection);
278        sendEvent(ConnectionEvent.CONNECTION_CLOSED, connection, null);
279    }
280
281    protected void sendTxStartedEvent(ConnectionImpl connection) {
282        sendEvent(ConnectionEvent.LOCAL_TRANSACTION_STARTED, connection, null);
283    }
284
285    protected void sendTxCommittedEvent(ConnectionImpl connection) {
286        sendEvent(ConnectionEvent.LOCAL_TRANSACTION_COMMITTED, connection, null);
287    }
288
289    protected void sendTxRolledbackEvent(ConnectionImpl connection) {
290        sendEvent(ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK, connection, null);
291    }
292
293    protected void sendErrorEvent(ConnectionImpl connection, Exception cause) {
294        sendEvent(ConnectionEvent.CONNECTION_ERROR_OCCURRED, connection, cause);
295    }
296
297    private void sendEvent(int type, ConnectionImpl connection, Exception cause) {
298        ConnectionEvent event = new ConnectionEvent(this, type, cause);
299        if (connection != null) {
300            event.setConnectionHandle(connection);
301        }
302        sendEvent(event);
303    }
304
305    /**
306     * Notifies the application server, through the {@link ConnectionEventListener}s it has registered with us, of what
307     * happens with this connection.
308     */
309    private void sendEvent(ConnectionEvent event) {
310        for (Object object : listeners.getListeners()) {
311            ConnectionEventListener listener = (ConnectionEventListener) object;
312            switch (event.getId()) {
313            case ConnectionEvent.CONNECTION_CLOSED:
314                listener.connectionClosed(event);
315                break;
316            case ConnectionEvent.LOCAL_TRANSACTION_STARTED:
317                listener.localTransactionStarted(event);
318                break;
319            case ConnectionEvent.LOCAL_TRANSACTION_COMMITTED:
320                listener.localTransactionCommitted(event);
321                break;
322            case ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK:
323                listener.localTransactionRolledback(event);
324                break;
325            case ConnectionEvent.CONNECTION_ERROR_OCCURRED:
326                listener.connectionErrorOccurred(event);
327                break;
328            }
329        }
330    }
331
332}