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 */
022
023 package org.granite.tide.spring;
024
025 import org.granite.gravity.Gravity;
026 import org.granite.logging.Logger;
027 import org.granite.tide.data.DataContext;
028 import org.granite.tide.data.DataEnabled;
029 import org.granite.tide.data.DataEnabled.PublishMode;
030 import org.granite.tide.data.DataUpdatePostprocessor;
031 import org.granite.tide.data.TideSynchronizationManager;
032 import org.granite.util.TypeUtil;
033 import org.springframework.transaction.support.TransactionSynchronizationAdapter;
034 import org.springframework.transaction.support.TransactionSynchronizationManager;
035
036 import java.util.HashMap;
037 import java.util.Map;
038 import java.util.concurrent.Callable;
039
040 /**
041 * Common class to implement data enabled interceptors
042 */
043 public class TideDataPublishingWrapper {
044
045 private static final Logger log = Logger.getLogger(TideDataPublishingWrapper.class);
046
047 private Map<String, TideSynchronizationManager> syncsMap = new HashMap<String, TideSynchronizationManager>();
048
049 private Gravity gravity;
050 private DataUpdatePostprocessor dataUpdatePostprocessor;
051
052 public void setGravity(Gravity gravity) {
053 this.gravity = gravity;
054 }
055
056 public void setDataUpdatePostprocessor(DataUpdatePostprocessor dataUpdatePostprocessor) {
057 this.dataUpdatePostprocessor = dataUpdatePostprocessor;
058 }
059
060 public TideDataPublishingWrapper() {
061 try {
062 syncsMap.put("org.springframework.orm.hibernate3.SessionHolder", TypeUtil.newInstance("org.granite.tide.spring.Hibernate3SynchronizationManager", TideSynchronizationManager.class));
063 }
064 catch (Throwable e) {
065 // Hibernate 3 not present
066 }
067 try {
068 syncsMap.put("org.springframework.orm.hibernate4.SessionHolder", TypeUtil.newInstance("org.granite.tide.spring.Hibernate4SynchronizationManager", TideSynchronizationManager.class));
069 }
070 catch (Throwable e) {
071 // Hibernate 4 not present
072 }
073 try {
074 syncsMap.put("org.springframework.orm.jpa.EntityManagerHolder", TypeUtil.newInstance("org.granite.tide.spring.JPASynchronizationManager", TideSynchronizationManager.class));
075 }
076 catch (Throwable e) {
077 // JPA not present
078 }
079 }
080
081 public TideDataPublishingWrapper(Gravity gravity, DataUpdatePostprocessor dataUpdatePostprocessor) {
082 this();
083 this.gravity = gravity;
084 this.dataUpdatePostprocessor = dataUpdatePostprocessor;
085 }
086
087 public <T> T execute(DataEnabled dataEnabled, Callable<T> action) {
088 boolean shouldRemoveContextAtEnd = DataContext.get() == null;
089 boolean shouldInitContext = shouldRemoveContextAtEnd || DataContext.isNull();
090 boolean onCommit = false;
091
092 if (shouldInitContext) {
093 DataContext.init(gravity, dataEnabled.topic(), dataEnabled.params(), dataEnabled.publish());
094 if (dataUpdatePostprocessor != null)
095 DataContext.get().setDataUpdatePostprocessor(dataUpdatePostprocessor);
096 }
097
098 DataContext.observe();
099 try {
100 if (dataEnabled.publish().equals(PublishMode.ON_COMMIT) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
101 if (TransactionSynchronizationManager.isSynchronizationActive()) {
102 boolean registered = false;
103 for (Object resource : TransactionSynchronizationManager.getResourceMap().values()) {
104 if (syncsMap.containsKey(resource.getClass().getName())) {
105 registered = syncsMap.get(resource.getClass().getName()).registerSynchronization(resource, shouldRemoveContextAtEnd);
106 break;
107 }
108 }
109 if (!registered)
110 TransactionSynchronizationManager.registerSynchronization(new DataPublishingTransactionSynchronization(shouldRemoveContextAtEnd));
111 else if (shouldRemoveContextAtEnd)
112 TransactionSynchronizationManager.registerSynchronization(new DataContextCleanupTransactionSynchronization());
113 onCommit = true;
114 }
115 else if (TransactionSynchronizationManager.isActualTransactionActive()) {
116 log.error("Could not register synchronization for ON_COMMIT publish mode, check that the Spring PlatformTransactionManager supports it "
117 + "and that the order of the TransactionInterceptor is lower than the order of TideDataPublishingInterceptor");
118 if (shouldRemoveContextAtEnd)
119 DataContext.remove();
120 }
121 else {
122 // No transaction, clear data context and wait for a possible manual transactions
123 if (shouldRemoveContextAtEnd)
124 DataContext.remove();
125 }
126 }
127
128 T ret = action.call();
129
130 DataContext.publish(PublishMode.ON_SUCCESS);
131 return ret;
132 }
133 catch (Exception e) {
134 if (e instanceof RuntimeException)
135 throw (RuntimeException)e;
136 throw new RuntimeException("Data publishing error", e);
137 }
138 finally {
139 if (shouldRemoveContextAtEnd && !onCommit)
140 DataContext.remove();
141 }
142 }
143
144 private static class DataPublishingTransactionSynchronization extends TransactionSynchronizationAdapter {
145
146 private boolean removeContext = false;
147
148 public DataPublishingTransactionSynchronization(boolean removeContext) {
149 this.removeContext = removeContext;
150 }
151
152 @Override
153 public void beforeCommit(boolean readOnly) {
154 if (!readOnly)
155 DataContext.publish(PublishMode.ON_COMMIT);
156 }
157
158 @Override
159 public void beforeCompletion() {
160 if (removeContext)
161 DataContext.remove();
162 }
163
164 @Override
165 public void afterCompletion(int status) {
166 if (removeContext)
167 DataContext.remove();
168 }
169 }
170
171 private static class DataContextCleanupTransactionSynchronization extends TransactionSynchronizationAdapter {
172
173 @Override
174 public void afterCompletion(int status) {
175 DataContext.remove();
176 }
177 }
178
179 }