001/*
002 * (C) Copyright 2016 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
017package org.nuxeo.runtime.jtajca;
018
019import java.sql.Connection;
020import java.sql.SQLException;
021import java.sql.Statement;
022
023import javax.resource.ResourceException;
024import javax.resource.spi.ManagedConnection;
025
026import org.apache.commons.logging.LogFactory;
027import org.apache.geronimo.connector.outbound.ConnectionInfo;
028import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
029import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
030import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
031import org.tranql.connector.AbstractManagedConnection;
032import org.tranql.connector.jdbc.ConnectionHandle;
033
034/**
035 *
036 *
037 * @since 8.3
038 */
039public class NuxeoValidationSupport {
040
041    final Validation onBorrow;
042
043    final Validation onReturn;
044
045    NuxeoValidationSupport(Validation onBorrow, Validation onReturn) {
046        this.onBorrow = onBorrow == null ? NOOP : onBorrow;
047        this.onReturn = onReturn == null ? NOOP : onReturn;
048    }
049
050    public interface Validation {
051        boolean validate(ManagedConnection mc);
052    }
053
054    static final Validation NOOP = new Validation() {
055        @Override
056        public boolean validate(ManagedConnection mc) {
057            return true;
058        }
059    };
060
061    public static class ValidSQLConnection implements Validation {
062        @Override
063        public boolean validate(ManagedConnection mc) {
064            try {
065                @SuppressWarnings("unchecked")
066                AbstractManagedConnection<Connection, ConnectionHandle> jdbcManagedConnection = (AbstractManagedConnection<Connection, ConnectionHandle>) mc;
067                return jdbcManagedConnection.getPhysicalConnection().isValid(0);
068            } catch (SQLException cause) {
069                return false;
070            }
071        }
072    }
073
074    public static class QuerySQLConnection implements Validation {
075        final String sql;
076
077        QuerySQLConnection(String sql) {
078            this.sql = sql;
079        }
080
081        @Override
082        public boolean validate(ManagedConnection mc) {
083            @SuppressWarnings("unchecked")
084            AbstractManagedConnection<Connection, ConnectionHandle> jdbcManagedConnection = (AbstractManagedConnection<Connection, ConnectionHandle>) mc;
085            try (Statement statement = jdbcManagedConnection.getPhysicalConnection().createStatement()) {
086                return statement.execute(sql);
087            } catch (SQLException cause) {
088                LogFactory.getLog(QuerySQLConnection.class)
089                        .warn(String.format("Caught error executing '%s', invalidating", sql), cause);
090                return false;
091            }
092        }
093    }
094
095    public ConnectionInterceptor addValidationInterceptors(ConnectionInterceptor stack) {
096        if (onBorrow == NOOP && onReturn == NOOP) {
097            return stack;
098        }
099        return new ValidationInterceptor(stack);
100    }
101
102    class ValidationInterceptor implements ConnectionInterceptor {
103
104        public ValidationInterceptor(ConnectionInterceptor next) {
105            this.next = next;
106        }
107
108        final ConnectionInterceptor next;
109
110        @Override
111        public void getConnection(ConnectionInfo ci) throws ResourceException {
112            while (true) {
113                // request for a connection
114                next.getConnection(ci);
115                // validate connection
116                if (onBorrow.validate(ci.getManagedConnectionInfo().getManagedConnection())) {
117                    return;
118                }
119                // destroy invalid connection and retry
120                LogFactory.getLog(NuxeoValidationSupport.class).warn("Returning invalid connection " + ci);
121                returnConnection(ci, ConnectionReturnAction.DESTROY);
122            }
123        }
124
125        @Override
126        public void returnConnection(ConnectionInfo info, ConnectionReturnAction returnAction) {
127            if (returnAction == ConnectionReturnAction.RETURN_HANDLE) {
128                if (!onReturn.validate(info.getManagedConnectionInfo().getManagedConnection())) {
129                    returnAction = ConnectionReturnAction.DESTROY;
130                }
131            }
132            try {
133                next.returnConnection(info, returnAction);
134            } finally {
135                if (returnAction == ConnectionReturnAction.DESTROY) {
136                    // recycle managed connection info for a new managed connection
137                    ManagedConnectionInfo mci = info.getManagedConnectionInfo();
138                    mci = new ManagedConnectionInfo(mci.getManagedConnectionFactory(), mci.getConnectionRequestInfo());
139                    info.setManagedConnectionInfo(mci);
140                }
141            }
142        }
143
144        @Override
145        public void info(StringBuilder s) {
146            next.info(s);
147        }
148
149        @Override
150        public void destroy() {
151            next.destroy();
152        }
153
154    }
155
156}