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}