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        try {
144            session.close();
145        } finally {
146            cleanup();
147        }
148    }
149
150    /**
151     * Called by the application server to change the association of an application-level {@link Connection} handle with
152     * a {@link ManagedConnection} instance.
153     */
154    @Override
155    public void associateConnection(Object object) throws ResourceException {
156        ConnectionImpl connection = (ConnectionImpl) object;
157        log.debug("associateConnection: " + this + ", connection: " + connection);
158        ManagedConnectionImpl other = connection.getManagedConnection();
159        if (other != this) {
160            log.debug("associateConnection other: " + other);
161            other.removeConnection(connection);
162            addConnection(connection);
163        }
164    }
165
166    @Override
167    public XAResource getXAResource() {
168        return xaresource;
169    }
170
171    @Override
172    public LocalTransaction getLocalTransaction() {
173        throw new UnsupportedOperationException("Local transactions not supported");
174    }
175
176    /**
177     * Called by the application server to add a listener who should be notified of all relevant events on this
178     * connection.
179     */
180    @Override
181    public void addConnectionEventListener(ConnectionEventListener listener) {
182        listeners.add(listener);
183    }
184
185    /**
186     * Called by the application server to remove a listener.
187     */
188    @Override
189    public void removeConnectionEventListener(ConnectionEventListener listener) {
190        listeners.remove(listener);
191    }
192
193    @Override
194    public ManagedConnectionMetaData getMetaData() {
195        return this;
196    }
197
198    @Override
199    public void setLogWriter(PrintWriter out) {
200        this.out = out;
201    }
202
203    @Override
204    public PrintWriter getLogWriter() {
205        return out;
206    }
207
208    /*
209     * ----- javax.resource.spi.ManagedConnectionMetaData -----
210     */
211
212    @Override
213    public String getEISProductName() {
214        return "Nuxeo Core SQL Storage";
215    }
216
217    @Override
218    public String getEISProductVersion() {
219        return "1.0.0";
220    }
221
222    @Override
223    public int getMaxConnections() {
224        return Integer.MAX_VALUE; // or lower?
225    }
226
227    @Override
228    public String getUserName() throws ResourceException {
229        return null;
230    }
231
232    /*
233     * ----- Internal -----
234     */
235
236    /**
237     * Adds a connection to those using this managed connection.
238     */
239    private void addConnection(ConnectionImpl connection) {
240        log.debug("addConnection: " + connection);
241        if (connections.add(connection) == false) {
242            throw new IllegalStateException("already known connection " + connection + " in " + this);
243        }
244        connection.setManagedConnection(this);
245        connection.associate(session);
246    }
247
248    /**
249     * Removes a connection to those using this managed connection.
250     */
251    private void removeConnection(ConnectionImpl connection) {
252        log.debug("removeConnection: " + connection);
253        if (connections.remove(connection) == false) {
254            throw new IllegalStateException("unknown connection " + connection + " in " + this);
255        }
256        connection.setManagedConnection(null);
257        connection.disassociate();
258    }
259
260    /**
261     * Called by {@link ConnectionImpl#close} when the connection is closed.
262     */
263    protected void close(ConnectionImpl connection) {
264        removeConnection(connection);
265        sendClosedEvent(connection);
266    }
267
268    /**
269     * Called by {@link ManagedConnectionFactoryImpl#matchManagedConnections}.
270     */
271    protected ManagedConnectionFactoryImpl getManagedConnectionFactory() {
272        return managedConnectionFactory;
273    }
274
275    /*
276     * ----- Event management -----
277     */
278
279    private void sendClosedEvent(ConnectionImpl connection) {
280        log.debug("closing a connection " + connection);
281        sendEvent(ConnectionEvent.CONNECTION_CLOSED, connection, null);
282    }
283
284    protected void sendTxStartedEvent(ConnectionImpl connection) {
285        sendEvent(ConnectionEvent.LOCAL_TRANSACTION_STARTED, connection, null);
286    }
287
288    protected void sendTxCommittedEvent(ConnectionImpl connection) {
289        sendEvent(ConnectionEvent.LOCAL_TRANSACTION_COMMITTED, connection, null);
290    }
291
292    protected void sendTxRolledbackEvent(ConnectionImpl connection) {
293        sendEvent(ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK, connection, null);
294    }
295
296    protected void sendErrorEvent(ConnectionImpl connection, Exception cause) {
297        sendEvent(ConnectionEvent.CONNECTION_ERROR_OCCURRED, connection, cause);
298    }
299
300    private void sendEvent(int type, ConnectionImpl connection, Exception cause) {
301        ConnectionEvent event = new ConnectionEvent(this, type, cause);
302        if (connection != null) {
303            event.setConnectionHandle(connection);
304        }
305        sendEvent(event);
306    }
307
308    /**
309     * Notifies the application server, through the {@link ConnectionEventListener}s it has registered with us, of what
310     * happens with this connection.
311     */
312    private void sendEvent(ConnectionEvent event) {
313        for (Object object : listeners.getListeners()) {
314            ConnectionEventListener listener = (ConnectionEventListener) object;
315            switch (event.getId()) {
316            case ConnectionEvent.CONNECTION_CLOSED:
317                listener.connectionClosed(event);
318                break;
319            case ConnectionEvent.LOCAL_TRANSACTION_STARTED:
320                listener.localTransactionStarted(event);
321                break;
322            case ConnectionEvent.LOCAL_TRANSACTION_COMMITTED:
323                listener.localTransactionCommitted(event);
324                break;
325            case ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK:
326                listener.localTransactionRolledback(event);
327                break;
328            case ConnectionEvent.CONNECTION_ERROR_OCCURRED:
329                listener.connectionErrorOccurred(event);
330                break;
331            }
332        }
333    }
334
335}