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
021 package org.granite.tide.spring;
022
023 import java.util.HashMap;
024 import java.util.Map;
025
026 import org.aopalliance.intercept.MethodInterceptor;
027 import org.aopalliance.intercept.MethodInvocation;
028 import org.granite.gravity.Gravity;
029 import org.granite.logging.Logger;
030 import org.granite.tide.data.DataContext;
031 import org.granite.tide.data.DataEnabled;
032 import org.granite.tide.data.DataEnabled.PublishMode;
033 import org.granite.tide.data.DataUpdatePostprocessor;
034 import org.granite.tide.data.TideSynchronizationManager;
035 import org.granite.util.TypeUtil;
036 import org.springframework.beans.factory.annotation.Autowired;
037 import org.springframework.transaction.support.TransactionSynchronizationAdapter;
038 import 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 */
048 public 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 }