001/*
002 * (C) Copyright 2014 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 *     Stephane Lacoin
018 */
019package org.nuxeo.ecm.core.storage.sql.jdbc;
020
021import java.lang.reflect.InvocationHandler;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Proxy;
025import java.util.Arrays;
026import java.util.function.Function;
027import java.util.function.Supplier;
028
029import javax.transaction.SystemException;
030import javax.transaction.xa.XAResource;
031
032import org.nuxeo.common.utils.ExceptionUtils;
033import org.nuxeo.ecm.core.storage.sql.Mapper;
034import org.nuxeo.runtime.transaction.TransactionHelper;
035
036public class JDBCMapperConnector implements InvocationHandler {
037
038    protected final Mapper mapper;
039
040    protected final boolean noSharing;
041
042    protected final Function<Supplier<Object>, Object> defaultRunner;
043
044    protected JDBCMapperConnector(Mapper mapper, boolean noSharing) {
045        this.mapper = mapper;
046        this.noSharing = noSharing;
047        defaultRunner = noSharing ? TransactionHelper::runInNewTransaction : TransactionHelper::runInTransaction;
048    }
049
050    protected Object doInvoke(Method method, Object[] args) throws Throwable {
051        try {
052            return method.invoke(mapper, args);
053        } catch (InvocationTargetException cause) {
054            return cause.getTargetException();
055        }
056    }
057
058    @Override
059    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
060        String name = method.getName();
061        if (mapper.isConnected()) {
062            if (Arrays.asList("start", "end", "prepare", "commit", "rollback").contains(name)) {
063                throw new SystemException("wrong tx management invoke on managed connection");
064            }
065            return doInvoke(method, args);
066        }
067        // should not operate with tx mamagement (managed connection)
068        if ("start".equals(name)) {
069            return XAResource.XA_OK;
070        }
071        if ("end".equals(name)) {
072            return null;
073        }
074        if ("prepare".equals(name)) {
075            return XAResource.XA_OK;
076        }
077        if ("commit".equals(name)) {
078            return null;
079        }
080        if ("rollback".equals(name)) {
081            return null;
082        }
083        if ("clearCache".equals(name)) {
084            return doInvoke(method, args);
085        }
086        if ("receiveInvalidations".equals(name)) {
087            return doInvoke(method, args);
088        }
089        if ("sendInvalidations".equals(name)) {
090            return doInvoke(method, args);
091        }
092        return doConnectAndInvoke(method, args);
093    }
094
095    protected Object doConnectAndInvoke(Method method, Object[] args) throws Throwable {
096        String name = method.getName();
097        Object result = runnerOf(name).apply(() -> {
098            mapper.connect(noSharingOf(name));
099            try {
100                try {
101                    return doInvoke(method, args);
102                } catch (Throwable cause) {
103                    return cause;
104                }
105            } finally {
106                if (mapper.isConnected()) {
107                    mapper.disconnect();
108                }
109            }
110        });
111        if (result instanceof Throwable) {
112            if (result instanceof Exception) {
113                ExceptionUtils.checkInterrupt((Exception) result);
114            }
115            throw (Throwable) result;
116        }
117        return result;
118    }
119
120    protected Function<Supplier<Object>, Object> runnerOf(String name) {
121        if ("createDatabase".equals(name)) {
122            return TransactionHelper::runWithoutTransaction;
123        }
124        return defaultRunner;
125    }
126
127    protected boolean noSharingOf(String name) {
128        if ("createDatabase".equals(name)) {
129            return true;
130        }
131        return noSharing;
132    }
133
134    public static Mapper newConnector(Mapper mapper, boolean noSharing) {
135        return (Mapper) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
136                new Class<?>[] { Mapper.class }, new JDBCMapperConnector(mapper, noSharing));
137    }
138
139    public static Mapper unwrap(Mapper mapper) {
140        if (!Proxy.isProxyClass(mapper.getClass())) {
141            return mapper;
142        }
143        return ((JDBCMapperConnector) Proxy.getInvocationHandler(mapper)).mapper;
144    }
145}