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    @Override
051    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
052        String name = method.getName();
053        if (mapper.isConnected()) {
054            if (Arrays.asList("start", "end", "prepare", "commit", "rollback").contains(name)) {
055                throw new SystemException("wrong tx management invoke on managed connection");
056            }
057            return doDirectInvoke(method, args);
058        }
059        // should not operate with tx mamagement (managed connection)
060        if ("start".equals(name)) {
061            return XAResource.XA_OK;
062        }
063        if ("end".equals(name)) {
064            return null;
065        }
066        if ("prepare".equals(name)) {
067            return XAResource.XA_OK;
068        }
069        if ("commit".equals(name)) {
070            return null;
071        }
072        if ("rollback".equals(name)) {
073            return null;
074        }
075        if ("clearCache".equals(name)) {
076            return doDirectInvoke(method, args);
077        }
078        if ("receiveInvalidations".equals(name)) {
079            return doDirectInvoke(method, args);
080        }
081        if ("sendInvalidations".equals(name)) {
082            return doDirectInvoke(method, args);
083        }
084        return doConnectAndInvoke(method, args);
085    }
086
087    protected Object doDirectInvoke(Method method, Object[] args) throws Throwable {
088        Object result = doInvoke(method, args);
089        throwIfThrowable(result);
090        return result;
091    }
092
093    protected Object doConnectAndInvoke(Method method, Object[] args) throws Throwable {
094        String name = method.getName();
095        Object result = runnerOf(name).apply(() -> {
096            mapper.connect(noSharingOf(name));
097            try {
098                return doInvoke(method, args);
099            } finally {
100                if (mapper.isConnected()) {
101                    mapper.disconnect();
102                }
103            }
104        });
105        throwIfThrowable(result);
106        return result;
107    }
108
109    protected Object doInvoke(Method method, Object[] args) {
110        try {
111            return method.invoke(mapper, args);
112        } catch (InvocationTargetException cause) {
113            return cause.getTargetException();
114        } catch (Exception e) { // no need to catch Error
115            return e;
116        }
117    }
118
119    protected static void throwIfThrowable(Object result) throws Throwable {
120        if (result instanceof Throwable) {
121            if (result instanceof Exception) {
122                ExceptionUtils.checkInterrupt((Exception) result);
123            }
124            throw (Throwable) result;
125        }
126    }
127
128    protected Function<Supplier<Object>, Object> runnerOf(String name) {
129        if ("createDatabase".equals(name)) {
130            return TransactionHelper::runWithoutTransaction;
131        }
132        return defaultRunner;
133    }
134
135    protected boolean noSharingOf(String name) {
136        if ("createDatabase".equals(name)) {
137            return true;
138        }
139        return noSharing;
140    }
141
142    public static Mapper newConnector(Mapper mapper, boolean noSharing) {
143        return (Mapper) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
144                new Class<?>[] { Mapper.class }, new JDBCMapperConnector(mapper, noSharing));
145    }
146
147    public static Mapper unwrap(Mapper mapper) {
148        if (!Proxy.isProxyClass(mapper.getClass())) {
149            return mapper;
150        }
151        return ((JDBCMapperConnector) Proxy.getInvocationHandler(mapper)).mapper;
152    }
153}