001/*
002 * (C) Copyright 2006-2017 Nuxeo (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 */
019package org.nuxeo.ecm.core.storage.sql.ra;
020
021import java.io.Serializable;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import javax.resource.ResourceException;
028import javax.resource.cci.ConnectionFactory;
029import javax.resource.cci.ConnectionMetaData;
030import javax.resource.cci.Interaction;
031import javax.resource.cci.LocalTransaction;
032import javax.resource.cci.ResultSetInfo;
033
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.api.IterableQueryResult;
036import org.nuxeo.ecm.core.api.NuxeoException;
037import org.nuxeo.ecm.core.api.PartialList;
038import org.nuxeo.ecm.core.api.ScrollResult;
039import org.nuxeo.ecm.core.model.LockManager;
040import org.nuxeo.ecm.core.query.QueryFilter;
041import org.nuxeo.ecm.core.storage.sql.Mapper;
042import org.nuxeo.ecm.core.storage.sql.Model;
043import org.nuxeo.ecm.core.storage.sql.Node;
044import org.nuxeo.ecm.core.storage.sql.Session;
045import org.nuxeo.ecm.core.storage.sql.SessionImpl;
046
047/**
048 * A connection is a handle to the underlying storage. It is returned by the {@link ConnectionFactory} to application
049 * code.
050 * <p>
051 * The actual link to the underlying storage ({@link Session}) is provided by the
052 * {@link javax.resource.spi.ManagedConnection} which created this {@link javax.resource.cci.Connection}.
053 *
054 * @author Florent Guillaume
055 */
056public class ConnectionImpl implements Session {
057
058    private ManagedConnectionImpl managedConnection;
059
060    private SessionImpl session;
061
062    public ConnectionImpl(ManagedConnectionImpl managedConnection) {
063        this.managedConnection = managedConnection;
064    }
065
066    /*
067     * ----- callbacks -----
068     */
069
070    /**
071     * Called by {@link ManagedConnectionImpl#associateConnection}.
072     */
073    protected ManagedConnectionImpl getManagedConnection() {
074        return managedConnection;
075    }
076
077    /**
078     * Called by {@link ManagedConnectionImpl#associateConnection}.
079     */
080    protected void setManagedConnection(ManagedConnectionImpl managedConnection) {
081        this.managedConnection = managedConnection;
082    }
083
084    /**
085     * Called by {@link ManagedConnectionImpl#addConnection}.
086     */
087    protected void associate(SessionImpl session) {
088        this.session = session;
089    }
090
091    /**
092     * Called by {@link ManagedConnectionImpl#removeConnection}.
093     */
094    protected void disassociate() {
095        closeStillOpenQueryResults();
096        session = null;
097    }
098
099    /*
100     * ----- javax.resource.cci.Connection -----
101     */
102
103    protected Throwable closeTrace;
104
105    @Override
106    public void close() throws ResourceException {
107        if (managedConnection == null) {
108            IllegalStateException error = new IllegalStateException("connection already closed " + this);
109            error.addSuppressed(closeTrace);
110            throw error;
111        }
112        try {
113            managedConnection.close(this);
114        } finally {
115            closeTrace = new Throwable("close stack trace");
116            managedConnection = null;
117        }
118    }
119
120    @Override
121    public Interaction createInteraction() throws ResourceException {
122        throw new UnsupportedOperationException();
123    }
124
125    @Override
126    public LocalTransaction getLocalTransaction() throws ResourceException {
127        throw new UnsupportedOperationException();
128    }
129
130    @Override
131    public ConnectionMetaData getMetaData() throws ResourceException {
132        throw new UnsupportedOperationException();
133    }
134
135    @Override
136    public ResultSetInfo getResultSetInfo() throws ResourceException {
137        throw new UnsupportedOperationException();
138    }
139
140    /*
141     * ----- org.nuxeo.ecm.core.storage.sql.Session -----
142     */
143
144    private Session getSession() {
145        if (session == null) {
146            throw new NuxeoException("Cannot use closed connection handle: " + this);
147        }
148        return session;
149    }
150
151    @Override
152    public Mapper getMapper() {
153        return getSession().getMapper();
154    }
155
156    @Override
157    public boolean isLive() {
158        return session != null && session.isLive();
159    }
160
161    @Override
162    public String getRepositoryName() {
163        return getSession().getRepositoryName();
164    }
165
166    @Override
167    public Model getModel() {
168        return getSession().getModel();
169    }
170
171    @Override
172    public void save() {
173        getSession().save();
174    }
175
176    @Override
177    public Node getRootNode() {
178        return getSession().getRootNode();
179    }
180
181    @Override
182    public Node getNodeById(Serializable id) {
183        return getSession().getNodeById(id);
184    }
185
186    @Override
187    public List<Node> getNodesByIds(List<Serializable> ids) {
188        return getSession().getNodesByIds(ids);
189    }
190
191    @Override
192    public Node getNodeByPath(String path, Node node) {
193        return getSession().getNodeByPath(path, node);
194    }
195
196    @Override
197    public boolean addMixinType(Node node, String mixin) {
198        return getSession().addMixinType(node, mixin);
199    }
200
201    @Override
202    public boolean removeMixinType(Node node, String mixin) {
203        return getSession().removeMixinType(node, mixin);
204    }
205
206    @Override
207    public ScrollResult<String> scroll(String query, int batchSize, int keepAliveSeconds) {
208        return getSession().scroll(query, batchSize, keepAliveSeconds);
209    }
210
211    @Override
212    public ScrollResult<String> scroll(String scrollId) {
213        return getSession().scroll(scrollId);
214    }
215
216    @Override
217    public boolean hasChildNode(Node parent, String name, boolean complexProp) {
218        return getSession().hasChildNode(parent, name, complexProp);
219    }
220
221    @Override
222    public Node getChildNode(Node parent, String name, boolean complexProp) {
223        return getSession().getChildNode(parent, name, complexProp);
224    }
225
226    @Override
227    public boolean hasChildren(Node parent, boolean complexProp) {
228        return getSession().hasChildren(parent, complexProp);
229    }
230
231    @Override
232    public List<Node> getChildren(Node parent, String name, boolean complexProp) {
233        return getSession().getChildren(parent, name, complexProp);
234    }
235
236    @Override
237    public Node addChildNode(Node parent, String name, Long pos, String typeName, boolean complexProp) {
238        return getSession().addChildNode(parent, name, pos, typeName, complexProp);
239    }
240
241    @Override
242    public Node addChildNode(Serializable id, Node parent, String name, Long pos, String typeName,
243            boolean complexProp) {
244        return getSession().addChildNode(id, parent, name, pos, typeName, complexProp);
245    }
246
247    @Override
248    public void removeNode(Node node) {
249        getSession().removeNode(node);
250    }
251
252    @Override
253    public void removePropertyNode(Node node) {
254        getSession().removePropertyNode(node);
255    }
256
257    @Override
258    public Node getParentNode(Node node) {
259        return getSession().getParentNode(node);
260    }
261
262    @Override
263    public String getPath(Node node) {
264        return getSession().getPath(node);
265    }
266
267    @Override
268    public void orderBefore(Node node, Node src, Node dest) {
269        getSession().orderBefore(node, src, dest);
270    }
271
272    @Override
273    public Node move(Node source, Node parent, String name) {
274        return getSession().move(source, parent, name);
275    }
276
277    @Override
278    public Node copy(Node source, Node parent, String name) {
279        return getSession().copy(source, parent, name);
280    }
281
282    @Override
283    public Node checkIn(Node node, String label, String checkinComment) {
284        return getSession().checkIn(node, label, checkinComment);
285    }
286
287    @Override
288    public void checkOut(Node node) {
289        getSession().checkOut(node);
290    }
291
292    @Override
293    public void restore(Node node, Node version) {
294        getSession().restore(node, version);
295    }
296
297    @Override
298    public Node getVersionByLabel(Serializable versionSeriesId, String label) {
299        return getSession().getVersionByLabel(versionSeriesId, label);
300    }
301
302    @Override
303    public List<Node> getVersions(Serializable versionSeriesId) {
304        return getSession().getVersions(versionSeriesId);
305    }
306
307    @Override
308    public Node getLastVersion(Serializable versionSeriesId) {
309        return getSession().getLastVersion(versionSeriesId);
310    }
311
312    @Override
313    public List<Node> getProxies(Node document, Node parent) {
314        return getSession().getProxies(document, parent);
315    }
316
317    @Override
318    public void setProxyTarget(Node proxy, Serializable targetId) {
319        getSession().setProxyTarget(proxy, targetId);
320    }
321
322    @Override
323    public Node addProxy(Serializable targetId, Serializable versionSeriesId, Node parent, String name, Long pos) {
324        return getSession().addProxy(targetId, versionSeriesId, parent, name, pos);
325    }
326
327    @Override
328    public PartialList<Serializable> query(String query, QueryFilter queryFilter, boolean countTotal) {
329        return getSession().query(query, queryFilter, countTotal);
330    }
331
332    @Override
333    public PartialList<Serializable> query(String query, String queryType, QueryFilter queryFilter, long countUpTo) {
334        return getSession().query(query, queryType, queryFilter, countUpTo);
335    }
336
337    @Override
338    public IterableQueryResult queryAndFetch(String query, String queryType, QueryFilter queryFilter,
339            Object... params) {
340        IterableQueryResult result = getSession().queryAndFetch(query, queryType, queryFilter, params);
341        noteQueryResult(result);
342        return result;
343    }
344
345    @Override
346    public IterableQueryResult queryAndFetch(String query, String queryType, QueryFilter queryFilter,
347            boolean distinctDocuments, Object... params) {
348        IterableQueryResult result = getSession().queryAndFetch(query, queryType, queryFilter, distinctDocuments,
349                params);
350        noteQueryResult(result);
351        return result;
352    }
353
354    @Override
355    public PartialList<Map<String,Serializable>> queryProjection(String query, String queryType, QueryFilter queryFilter,
356            boolean distinctDocuments, long countUpTo, Object... params) {
357        return getSession().queryProjection(query, queryType, queryFilter, distinctDocuments, countUpTo, params);
358    }
359
360    public static class QueryResultContextException extends Exception {
361        private static final long serialVersionUID = 1L;
362
363        public final IterableQueryResult queryResult;
364
365        public QueryResultContextException(IterableQueryResult queryResult) {
366            super("queryAndFetch call context");
367            this.queryResult = queryResult;
368        }
369    }
370
371    protected final Set<QueryResultContextException> queryResults = new HashSet<>();
372
373    protected void noteQueryResult(IterableQueryResult result) {
374        queryResults.add(new QueryResultContextException(result));
375    }
376
377    protected void closeStillOpenQueryResults() {
378        for (QueryResultContextException context : queryResults) {
379            if (!context.queryResult.mustBeClosed()) {
380                continue;
381            }
382            try {
383                context.queryResult.close();
384            } catch (RuntimeException e) {
385                LogFactory.getLog(ConnectionImpl.class).error("Cannot close query result", e);
386            } finally {
387                LogFactory.getLog(ConnectionImpl.class)
388                          .warn("Closing a query results for you, check stack trace for allocating point", context);
389            }
390        }
391        queryResults.clear();
392    }
393
394    @Override
395    public LockManager getLockManager() {
396        return getSession().getLockManager();
397    }
398
399    @Override
400    public void requireReadAclsUpdate() {
401        if (session != null) {
402            session.requireReadAclsUpdate();
403        }
404    }
405
406    @Override
407    public void updateReadAcls() {
408        getSession().updateReadAcls();
409    }
410
411    @Override
412    public void rebuildReadAcls() {
413        getSession().rebuildReadAcls();
414    }
415
416    @Override
417    public Map<String, String> getBinaryFulltext(Serializable id) {
418        return getSession().getBinaryFulltext(id);
419    }
420
421    @Override
422    public boolean isChangeTokenEnabled() {
423        return getSession().isChangeTokenEnabled();
424    }
425
426    @Override
427    public void markUserChange(Serializable id) {
428        getSession().markUserChange(id);
429    }
430
431}