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}