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.ConnectionRequestInfo;
025import javax.resource.spi.ManagedConnectionFactory;
026
027import org.apache.commons.logging.LogFactory;
028import org.apache.geronimo.connector.outbound.ConnectionInfo;
029import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
030import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
031import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
032
033/**
034 *
035 *
036 * @since 8.3
037 */
038public class NuxeoValidationSupport {
039
040    final Validation onBorrow;
041
042    final Validation onReturn;
043
044    NuxeoValidationSupport(Validation onBorrow, Validation onReturn) {
045        this.onBorrow = onBorrow == null ? NOOP : onBorrow;
046        this.onReturn = onReturn == null ? NOOP : onReturn;
047    }
048
049    interface Validation {
050        boolean validate(Object handle);
051
052    }
053
054    static final Validation NOOP = new Validation() {
055        @Override
056        public boolean validate(Object handle) {
057            return true;
058        }
059    };
060
061    static class ValidSQLConnection implements Validation {
062        @Override
063        public boolean validate(Object handle) {
064            try {
065                return ((Connection) handle).isValid(0);
066            } catch (SQLException cause) {
067                return false;
068            }
069        }
070    }
071
072    static class QuerySQLConnection implements Validation {
073        final String sql;
074
075        QuerySQLConnection(String sql) {
076            this.sql = sql;
077        }
078
079        @Override
080        public boolean validate(Object handle) {
081            try (Statement statement = ((Connection) handle).unwrap(Connection.class).createStatement()) {
082                return statement.execute(sql);
083            } catch (SQLException cause) {
084                LogFactory.getLog(QuerySQLConnection.class).warn(String.format("Caught error executing '%s', invalidating", sql), cause);
085                return false;
086            }
087        }
088    }
089
090    public ConnectionInterceptor addTransactionInterceptor(ConnectionInterceptor stack) {
091        if (onBorrow == NOOP && onReturn == NOOP) {
092            return stack;
093        }
094        return new ValidationHandleInterceptor(stack);
095    }
096
097    class ValidationHandleInterceptor implements ConnectionInterceptor {
098
099        public ValidationHandleInterceptor(ConnectionInterceptor next) {
100            this.next = next;
101        }
102
103        final ConnectionInterceptor next;
104
105        @Override
106        public void getConnection(ConnectionInfo ci) throws ResourceException {
107            ManagedConnectionInfo mci = ci.getManagedConnectionInfo();
108            ManagedConnectionFactory mcf = mci.getManagedConnectionFactory();
109            ConnectionRequestInfo cri = mci.getConnectionRequestInfo();
110            while (true) {
111                // request for a connection
112                ConnectionInfo tryee = new ConnectionInfo(new ManagedConnectionInfo(mcf, cri));
113                next.getConnection(tryee);
114                // validate connection
115                Object handle = tryee.getConnectionProxy();
116                if (handle == null) {
117                    handle = tryee.getConnectionHandle();
118                }
119                if (onBorrow.validate(handle)) {
120                    // save handle an return connection
121                    if (tryee.getConnectionProxy() != null) {
122                        ci.setConnectionProxy(handle);
123                    } else {
124                        ci.setConnectionHandle(handle);
125                    }
126                    return;
127                }
128                // destroy invalid connection and retry
129                LogFactory.getLog(NuxeoValidationSupport.class).warn("Returning invalid connection " + tryee);
130                returnConnection(tryee, ConnectionReturnAction.DESTROY);
131            }
132        }
133
134        @Override
135        public void returnConnection(ConnectionInfo info, ConnectionReturnAction returnAction) {
136            if (returnAction == ConnectionReturnAction.RETURN_HANDLE) {
137                if (!onReturn.validate(info.getConnectionHandle())) {
138                    returnAction = ConnectionReturnAction.DESTROY;
139                }
140            }
141            next.returnConnection(info, returnAction);
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}