001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.tide.spring; 023 024import java.util.HashMap; 025import java.util.Map; 026 027import org.aopalliance.intercept.MethodInterceptor; 028import org.aopalliance.intercept.MethodInvocation; 029import org.granite.gravity.Gravity; 030import org.granite.logging.Logger; 031import org.granite.tide.data.DataContext; 032import org.granite.tide.data.DataEnabled; 033import org.granite.tide.data.DataEnabled.PublishMode; 034import org.granite.tide.data.DataUpdatePostprocessor; 035import org.granite.tide.data.TideSynchronizationManager; 036import org.granite.util.TypeUtil; 037import org.springframework.beans.factory.annotation.Autowired; 038import org.springframework.transaction.support.TransactionSynchronizationAdapter; 039import org.springframework.transaction.support.TransactionSynchronizationManager; 040 041/** 042 * Spring AOP interceptor to handle publishing of data changes instead of relying on the default behaviour 043 * This can be used outside of a HTTP Granite context and inside the security/transaction context 044 * 045 * Ensure that the order of the interceptor is correctly setup to use ON_COMMIT publish mode: this interceptor must be executed inside a transaction 046 * 047 * @author William DRAI 048 */ 049public class TideDataPublishingInterceptor implements MethodInterceptor { 050 051 private static final Logger log = Logger.getLogger(TideDataPublishingInterceptor.class); 052 053 private Gravity gravity; 054 private DataUpdatePostprocessor dataUpdatePostprocessor; 055 056 private Map<String, TideSynchronizationManager> syncsMap = new HashMap<String, TideSynchronizationManager>(); 057 058 public TideDataPublishingInterceptor() { 059 try { 060 syncsMap.put("org.springframework.orm.hibernate3.SessionHolder", TypeUtil.newInstance("org.granite.tide.spring.Hibernate3SynchronizationManager", TideSynchronizationManager.class)); 061 } 062 catch (Throwable e) { 063 // Hibernate 3 not present 064 } 065 try { 066 syncsMap.put("org.springframework.orm.hibernate4.SessionHolder", TypeUtil.newInstance("org.granite.tide.spring.Hibernate4SynchronizationManager", TideSynchronizationManager.class)); 067 } 068 catch (Throwable e) { 069 // Hibernate 4 not present 070 } 071 try { 072 syncsMap.put("org.springframework.orm.jpa.EntityManagerHolder", TypeUtil.newInstance("org.granite.tide.spring.JPASynchronizationManager", TideSynchronizationManager.class)); 073 } 074 catch (Throwable e) { 075 // JPA not present 076 } 077 } 078 079 080 public void setGravity(Gravity gravity) { 081 this.gravity = gravity; 082 } 083 084 @Autowired(required=false) 085 public void setDataUpdatePostprocessor(DataUpdatePostprocessor dataUpdatePostprocessor) { 086 this.dataUpdatePostprocessor = dataUpdatePostprocessor; 087 } 088 089 public Object invoke(final MethodInvocation invocation) throws Throwable { 090 DataEnabled dataEnabled = invocation.getThis().getClass().getAnnotation(DataEnabled.class); 091 if (dataEnabled == null || !dataEnabled.useInterceptor()) 092 return invocation.proceed(); 093 094 boolean shouldRemoveContextAtEnd = DataContext.get() == null; 095 boolean shouldInitContext = shouldRemoveContextAtEnd || DataContext.isNull(); 096 boolean onCommit = false; 097 098 if (shouldInitContext) { 099 DataContext.init(gravity, dataEnabled.topic(), dataEnabled.params(), dataEnabled.publish()); 100 if (dataUpdatePostprocessor != null) 101 DataContext.get().setDataUpdatePostprocessor(dataUpdatePostprocessor); 102 } 103 104 DataContext.observe(); 105 try { 106 if (dataEnabled.publish().equals(PublishMode.ON_COMMIT) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 107 if (TransactionSynchronizationManager.isSynchronizationActive()) { 108 boolean registered = false; 109 for (Object resource : TransactionSynchronizationManager.getResourceMap().values()) { 110 if (syncsMap.containsKey(resource.getClass().getName())) { 111 registered = syncsMap.get(resource.getClass().getName()).registerSynchronization(resource, shouldRemoveContextAtEnd); 112 break; 113 } 114 } 115 if (!registered) 116 TransactionSynchronizationManager.registerSynchronization(new DataPublishingTransactionSynchronization(shouldRemoveContextAtEnd)); 117 else if (shouldRemoveContextAtEnd) 118 TransactionSynchronizationManager.registerSynchronization(new DataContextCleanupTransactionSynchronization()); 119 onCommit = true; 120 } 121 else { 122 log.error("Could not register synchronization for ON_COMMIT publish mode, check that the Spring PlatformTransactionManager supports it " 123 + "and that the order of the TransactionInterceptor is lower than the order of TideDataPublishingInterceptor"); 124 } 125 } 126 127 Object ret = invocation.proceed(); 128 129 DataContext.publish(PublishMode.ON_SUCCESS); 130 return ret; 131 } 132 finally { 133 if (shouldRemoveContextAtEnd && !onCommit) 134 DataContext.remove(); 135 } 136 } 137 138 private static class DataPublishingTransactionSynchronization extends TransactionSynchronizationAdapter { 139 140 private boolean removeContext = false; 141 142 public DataPublishingTransactionSynchronization(boolean removeContext) { 143 this.removeContext = removeContext; 144 } 145 146 @Override 147 public void beforeCommit(boolean readOnly) { 148 if (!readOnly) 149 DataContext.publish(PublishMode.ON_COMMIT); 150 } 151 152 @Override 153 public void beforeCompletion() { 154 if (removeContext) 155 DataContext.remove(); 156 } 157 158 @Override 159 public void afterCompletion(int status) { 160 if (removeContext) 161 DataContext.remove(); 162 } 163 } 164 165 private static class DataContextCleanupTransactionSynchronization extends TransactionSynchronizationAdapter { 166 167 @Override 168 public void afterCompletion(int status) { 169 DataContext.remove(); 170 } 171 } 172}