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
023package org.granite.tide.spring;
024
025import org.granite.gravity.Gravity;
026import org.granite.logging.Logger;
027import org.granite.tide.data.DataContext;
028import org.granite.tide.data.DataEnabled;
029import org.granite.tide.data.DataEnabled.PublishMode;
030import org.granite.tide.data.DataUpdatePostprocessor;
031import org.granite.tide.data.TideSynchronizationManager;
032import org.granite.util.ThrowableCallable;
033import org.granite.util.TypeUtil;
034import org.springframework.transaction.support.TransactionSynchronizationAdapter;
035import org.springframework.transaction.support.TransactionSynchronizationManager;
036
037import java.util.HashMap;
038import java.util.Map;
039
040/**
041 * Common class to implement data enabled interceptors
042 */
043public 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, ThrowableCallable<T> action) throws Throwable {
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        finally {
134            if (shouldRemoveContextAtEnd && !onCommit)
135                DataContext.remove();
136        }
137    }
138
139    private static class DataPublishingTransactionSynchronization extends TransactionSynchronizationAdapter {
140
141        private boolean removeContext = false;
142
143        public DataPublishingTransactionSynchronization(boolean removeContext) {
144            this.removeContext = removeContext;
145        }
146
147        @Override
148        public void beforeCommit(boolean readOnly) {
149            if (!readOnly)
150                DataContext.publish(PublishMode.ON_COMMIT);
151        }
152
153        @Override
154        public void beforeCompletion() {
155            if (removeContext)
156                DataContext.remove();
157        }
158
159        @Override
160        public void afterCompletion(int status) {
161            if (removeContext)
162                DataContext.remove();
163        }
164    }
165
166    private static class DataContextCleanupTransactionSynchronization extends TransactionSynchronizationAdapter {
167
168        @Override
169        public void afterCompletion(int status) {
170            DataContext.remove();
171        }
172    }
173
174}