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