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