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    }