/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stephane Lacoin
*/
package org.nuxeo.ecm.core.storage.sql.jdbc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.transaction.SystemException;
import javax.transaction.xa.XAResource;
import org.nuxeo.common.utils.ExceptionUtils;
import org.nuxeo.ecm.core.storage.sql.Mapper;
import org.nuxeo.runtime.transaction.TransactionHelper;
public class JDBCMapperConnector implements InvocationHandler {
protected final Mapper mapper;
protected final boolean noSharing;
protected final Function<Supplier<Object>, Object> defaultRunner;
protected JDBCMapperConnector(Mapper mapper, boolean noSharing) {
this.mapper = mapper;
this.noSharing = noSharing;
defaultRunner = noSharing ? TransactionHelper::runInNewTransaction : TransactionHelper::runInTransaction;
}
protected Object doInvoke(Method method, Object[] args) throws Throwable {
try {
return method.invoke(mapper, args);
} catch (InvocationTargetException cause) {
return cause.getTargetException();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if (mapper.isConnected()) {
if (Arrays.asList("start", "end", "prepare", "commit", "rollback").contains(name)) {
throw new SystemException("wrong tx management invoke on managed connection");
}
return doInvoke(method, args);
}
// should not operate with tx mamagement (managed connection)
if ("start".equals(name)) {
return XAResource.XA_OK;
}
if ("end".equals(name)) {
return null;
}
if ("prepare".equals(name)) {
return XAResource.XA_OK;
}
if ("commit".equals(name)) {
return null;
}
if ("rollback".equals(name)) {
return null;
}
if ("clearCache".equals(name)) {
return doInvoke(method, args);
}
if ("receiveInvalidations".equals(name)) {
return doInvoke(method, args);
}
if ("sendInvalidations".equals(name)) {
return doInvoke(method, args);
}
return doConnectAndInvoke(method, args);
}
protected Object doConnectAndInvoke(Method method, Object[] args) throws Throwable {
String name = method.getName();
Object result = runnerOf(name).apply(() -> {
mapper.connect(noSharingOf(name));
try {
try {
return doInvoke(method, args);
} catch (Throwable cause) {
return cause;
}
} finally {
if (mapper.isConnected()) {
mapper.disconnect();
}
}
});
if (result instanceof Throwable) {
if (result instanceof Exception) {
ExceptionUtils.checkInterrupt((Exception) result);
}
throw (Throwable) result;
}
return result;
}
protected Function<Supplier<Object>, Object> runnerOf(String name) {
if ("createDatabase".equals(name)) {
return TransactionHelper::runWithoutTransaction;
}
return defaultRunner;
}
protected boolean noSharingOf(String name) {
if ("createDatabase".equals(name)) {
return true;
}
return noSharing;
}
public static Mapper newConnector(Mapper mapper, boolean noSharing) {
return (Mapper) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { Mapper.class }, new JDBCMapperConnector(mapper, noSharing));
}
public static Mapper unwrap(Mapper mapper) {
if (!Proxy.isProxyClass(mapper.getClass())) {
return mapper;
}
return ((JDBCMapperConnector) Proxy.getInvocationHandler(mapper)).mapper;
}
}