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 */
022package org.granite.tide.spring;
023
024import org.aspectj.lang.ProceedingJoinPoint;
025import org.aspectj.lang.annotation.Around;
026import org.aspectj.lang.annotation.Aspect;
027import org.granite.gravity.Gravity;
028import org.granite.logging.Logger;
029import org.granite.tide.data.DataContext;
030import org.granite.tide.data.DataEnabled;
031import org.granite.tide.data.DataEnabled.PublishMode;
032import org.granite.tide.data.DataUpdatePostprocessor;
033import org.springframework.beans.factory.annotation.Autowired;
034import org.springframework.core.Ordered;
035import org.springframework.transaction.support.TransactionSynchronizationAdapter;
036import org.springframework.transaction.support.TransactionSynchronizationManager;
037
038/**
039 * Spring AOP AspectJ aspect to handle publishing of data changes instead of relying on the default behaviour
040 * This can be used outside of a HTTP Granite context and inside the security/transaction context
041 *  
042 * @author William DRAI
043 */
044@Aspect
045public class TideDataPublishingAspect implements Ordered {
046        
047        private static final Logger log = Logger.getLogger(TideDataPublishingAspect.class);
048
049        private int order = 0;
050        private Gravity gravity;
051        private DataUpdatePostprocessor dataUpdatePostprocessor;
052        
053        public void setGravity(Gravity gravity) {
054                this.gravity = gravity;
055        }
056        
057        @Autowired(required=false)
058        public void setDataUpdatePostprocessor(DataUpdatePostprocessor dataUpdatePostprocessor) {
059                this.dataUpdatePostprocessor = dataUpdatePostprocessor;
060        }
061
062    public int getOrder() {
063        return order;
064    }
065    public void setOrder(int order) {
066        this.order = order;
067    }
068        
069        @Around("@within(dataEnabled)")
070    public Object invoke(ProceedingJoinPoint pjp, DataEnabled dataEnabled) throws Throwable {
071        if (dataEnabled == null || !dataEnabled.useInterceptor())
072                return pjp.proceed();
073        
074        boolean shouldRemoveContextAtEnd = DataContext.get() == null;
075        boolean shouldInitContext = shouldRemoveContextAtEnd || DataContext.isNull();
076        boolean onCommit = false;
077        
078        if (shouldInitContext) {
079                DataContext.init(gravity, dataEnabled.topic(), dataEnabled.params(), dataEnabled.publish());
080                if (dataUpdatePostprocessor != null)
081                        DataContext.get().setDataUpdatePostprocessor(dataUpdatePostprocessor);
082        }
083        
084        DataContext.observe();
085        try {
086                if (dataEnabled.publish().equals(PublishMode.ON_COMMIT) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
087                         if (TransactionSynchronizationManager.isSynchronizationActive()) {
088                                 TransactionSynchronizationManager.registerSynchronization(new DataPublishingTransactionSynchronization(shouldRemoveContextAtEnd));
089                                 onCommit = true;
090                         }
091                         else
092                                 log.warn("Could not register synchronization for ON_COMMIT publish mode, check that the Spring PlatformTransactionManager supports it");
093                }
094                
095                Object ret = pjp.proceed();
096                
097                DataContext.publish(PublishMode.ON_SUCCESS);
098                return ret;
099        }
100        finally {
101                if (shouldRemoveContextAtEnd && !onCommit)
102                        DataContext.remove();
103        }
104    }
105    
106    private static class DataPublishingTransactionSynchronization extends TransactionSynchronizationAdapter {
107        
108        private boolean removeContext = false;
109        
110        public DataPublishingTransactionSynchronization(boolean removeContext) {
111                this.removeContext = removeContext;
112        }
113
114                @Override
115                public void beforeCommit(boolean readOnly) {
116                        if (!readOnly)
117                                DataContext.publish(PublishMode.ON_COMMIT);
118                }
119
120                @Override
121                public void beforeCompletion() {
122                        if (removeContext)
123                                DataContext.remove();
124                }       
125    }
126}