001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2014 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.seam;
023
024import java.lang.annotation.Annotation;
025import java.lang.reflect.Field;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.granite.tide.TidePersistenceManager;
032import org.granite.tide.annotations.BypassTideInterceptor;
033import org.granite.tide.invocation.ContextUpdate;
034import org.granite.tide.seam.TideInit.FactoryVariable;
035import org.granite.tide.seam.lazy.SeamInitializer;
036import org.granite.tide.seam.lazy.TidePersistenceFactory;
037import org.jboss.seam.Component;
038import org.jboss.seam.ScopeType;
039import org.jboss.seam.Component.BijectedAttribute;
040import org.jboss.seam.annotations.DataBinderClass;
041import org.jboss.seam.annotations.In;
042import org.jboss.seam.annotations.Out;
043import org.jboss.seam.annotations.intercept.AroundInvoke;
044import org.jboss.seam.annotations.intercept.Interceptor;
045import org.jboss.seam.annotations.security.Restrict;
046import org.jboss.seam.bpm.BusinessProcessInterceptor;
047import org.jboss.seam.contexts.Context;
048import org.jboss.seam.contexts.Contexts;
049import org.jboss.seam.core.BijectionInterceptor;
050import org.jboss.seam.core.EventInterceptor;
051import org.jboss.seam.databinding.DataBinder;
052import org.jboss.seam.intercept.AbstractInterceptor;
053import org.jboss.seam.intercept.InvocationContext;
054import org.jboss.seam.log.LogProvider;
055import org.jboss.seam.log.Logging;
056
057
058/**
059 * This interceptor has 4 activities :
060 * - Updating the context with data received from the Flex client, remerging client data in the persistence context when needed
061 * - Intercept outjected values to return it to the client
062 * - Determine the Persistence Context being used for the conversation and creating a lazyinitializer
063 *   storing it in the current conversation
064 * - Return all changed values to the client
065 *
066 * @author Venkat DANDA
067 * @author Cameron INGRAM
068 * @author William DRAI
069 */
070
071
072@Interceptor(around={BijectionInterceptor.class, EventInterceptor.class, BusinessProcessInterceptor.class})
073public class TideInterceptor extends AbstractInterceptor {
074
075    private static final long serialVersionUID = 1L;
076
077    
078    private static final LogProvider log = Logging.getLogProvider(TideInterceptor.class);
079
080    private boolean reentrant; //OK, since all Seam components are single-threaded
081
082
083    @AroundInvoke
084    @SuppressWarnings({ "unchecked", "rawtypes" })
085    public Object aroundInvoke(InvocationContext invocation) throws Exception {
086
087        if (reentrant) {
088            log.trace("About to invoke method");
089
090            if (log.isTraceEnabled())
091                log.trace("reentrant call to component: " + getComponent().getName() );
092
093            Object result = invocation.proceed();
094
095            log.trace("Method invoked");
096 
097            return result;
098        }
099
100        reentrant = true;
101  
102        try {
103            if (getComponent().getBeanClass().isAnnotationPresent(BypassTideInterceptor.class))
104                return invocation.proceed();
105            
106            TideInvocation tideInvocation = TideInvocation.get();
107            
108            if (tideInvocation == null || tideInvocation.isLocked())
109                return invocation.proceed();
110
111            AbstractSeamServiceContext tideContext = null;
112            if (Contexts.isSessionContextActive()) 
113                tideContext = (AbstractSeamServiceContext)Component.getInstance(AbstractSeamServiceContext.COMPONENT_NAME, true); 
114            
115            if (tideContext == null)
116                return invocation.proceed();
117            
118            // Ignore lifecycle methods
119            if (SeamUtils.isLifecycleMethod(getComponent(), invocation.getMethod())) {
120                tideInvocation.lock();
121
122                Object result = invocation.proceed();
123
124                tideInvocation.unlock();
125
126                return result;
127            }
128
129            
130            boolean evaluate = false;
131            
132            //Check for persistence
133            checkForPersistenceContexts(invocation);
134
135            // Ignore inner interceptions of other components during processing
136            if (tideInvocation.isEnabled() && !tideInvocation.isUpdated()) {
137                List<ContextUpdate> updates = new ArrayList<ContextUpdate>(tideInvocation.getUpdates());
138                tideInvocation.updated();
139                tideContext.restoreContext(updates, getComponent(), invocation.getTarget());
140                evaluate = true;
141
142                // Inject DataModel selections
143                Field field = getComponent().getClass().getDeclaredField("dataModelGetters");
144                field.setAccessible(true);
145                List<BijectedAttribute<?>> dataModelGetters = (List<BijectedAttribute<?>>)field.get(getComponent());
146                for (BijectedAttribute<?> getter : dataModelGetters) {
147                    Annotation dataModelAnn = getter.getAnnotation();
148                    DataBinder wrapper = dataModelAnn.annotationType().getAnnotation(DataBinderClass.class).value().newInstance();
149                    String name = getter.getName();
150                    ScopeType scope = wrapper.getVariableScope(dataModelAnn);
151                    if (scope == ScopeType.UNSPECIFIED) {
152                        scope = getComponent().getScope();
153                        if (scope == ScopeType.STATELESS)
154                            scope = ScopeType.EVENT;
155                    }
156                    Object dataModel = scope.getContext().get(name);
157                    if (dataModel != null && dataModel instanceof TideDataModel) {
158                        Field field2 = getComponent().getClass().getDeclaredField("dataModelSelectionSetters");
159                        field2.setAccessible(true);
160                        Map<String, BijectedAttribute<?>> setters = (Map<String, BijectedAttribute<?>>)field2.get(getComponent());
161                        BijectedAttribute setter = setters.get(name);
162                        if (setter != null) {
163                            Object value = setter.get(invocation.getTarget());
164                            ((TideDataModel)dataModel).setRowData(value);
165                        }
166                    }
167                }
168            }
169            
170            // Do invocation
171            Object result = invocation.proceed();
172
173            boolean restrict = getComponent().beanClassHasAnnotation(Restrict.class);
174            
175            // Intercept outjected values
176            if (getComponent().needsOutjection()) {               
177                List<BijectedAttribute<Out>> li = getComponent().getOutAttributes();
178                for (BijectedAttribute<Out> att: li) {
179                    ScopeType scope = att.getAnnotation().scope();
180                    if (ScopeType.UNSPECIFIED.equals(scope)) {
181                        Component outComponent = Component.forName(att.getName());
182                        if (outComponent != null)
183                            scope = outComponent.getScope();
184                        else
185                            scope = getComponent().getScope();
186                    }
187                    if (ScopeType.STATELESS.equals(scope))
188                        scope = ScopeType.EVENT;
189                    
190                    if (!(ScopeType.EVENT.equals(scope))) {
191                        Context context = Contexts.getEventContext();
192                        if (context.get(att.getName() + "_tide_unspecified_") != null) {
193                            context.remove(att.getName() + "_tide_unspecified_");
194                            context.remove(att.getName());
195                        }
196                    }    
197                    
198                    tideContext.addResultEval(new ScopedContextResult(att.getName(), null, scope, restrict));
199                }
200
201                Field field = getComponent().getClass().getDeclaredField("dataModelGetters");
202                field.setAccessible(true);
203                List<BijectedAttribute<?>> dataModelGetters = (List<BijectedAttribute<?>>)field.get(getComponent());
204                for (BijectedAttribute<?> getter : dataModelGetters) {
205                    Annotation anno = getter.getAnnotation();
206                    DataBinder wrapper = anno.annotationType().getAnnotation(DataBinderClass.class).value().newInstance();
207                    ScopeType scope = wrapper.getVariableScope(anno);
208                    if (ScopeType.UNSPECIFIED.equals(scope))
209                        scope = getComponent().getScope();
210                    if (ScopeType.STATELESS.equals(scope))
211                        scope = ScopeType.EVENT;
212                    
213                    if (!(ScopeType.EVENT.equals(scope))) {
214                        Context context = Contexts.getEventContext();
215                        if (context.get(getter.getName() + "_tide_unspecified_") != null) {
216                            context.remove(getter.getName() + "_tide_unspecified_");
217                            context.remove(getter.getName());
218                        }
219                    }
220                    
221                    tideContext.addResultEval(new ScopedContextResult(getter.getName(), null, scope, restrict));
222                }
223            }
224            
225            // Force evaluation of factory components dependent on the called component
226            Set<FactoryVariable> factoredVariables = TideInit.getFactoredVariables(getComponent());
227            if (factoredVariables != null) {
228                    for (FactoryVariable variable : factoredVariables) {
229                        ScopeType scope = variable.getScope();
230                        if (ScopeType.UNSPECIFIED.equals(scope))
231                                scope = getComponent().getScope();
232                        if (ScopeType.STATELESS.equals(scope))
233                                scope = ScopeType.EVENT;
234                        
235                        tideContext.addResultEval(new ScopedContextResult(variable.getVariableName(), null, scope, restrict));
236                    }
237            }
238            
239
240            if (evaluate)
241                tideInvocation.evaluated(tideContext.evaluateResults(getComponent(), invocation.getTarget(), false));
242
243            return result;
244        }
245        finally {
246            reentrant = false;
247        }
248    } 
249    
250    /**
251     * Try to determine what the PersistenceContext is and create an appropriate 
252     * lazy initializer for it.
253     * @param ctx the bean bieng accessed.
254     */
255    private void checkForPersistenceContexts(InvocationContext ctx) {        
256        Object bean = ctx.getTarget();
257        TidePersistenceManager pm = null;
258        
259        for ( BijectedAttribute<?> ba: getComponent().getPersistenceContextAttributes() ) {
260                    Object object = ba.get(bean);
261                    SeamInitializer.instance().setTidePersistenceManager(TidePersistenceFactory.createTidePersistence(getComponent(), object));
262                    return;
263        }
264        
265            if (getComponent().needsInjection()) {
266                    List<BijectedAttribute<In>> li = getComponent().getInAttributes();
267                    
268                        for (BijectedAttribute<In> att: li) {
269                                
270                                try {
271                                        pm = TidePersistenceFactory.createTidePersistence(getComponent(), att);
272                                } catch (RuntimeException ex) {
273                                        continue;
274                                }
275                                
276                                if (pm != null) {
277                                SeamInitializer.instance().setTidePersistenceManager(pm);
278                                return;
279                                } 
280                        }
281                }
282            
283            //Last chance to see a PersistenceManager can be found for this invocation
284            pm = TidePersistenceFactory.createTidePersistence(getComponent(), ctx.getTarget());
285            if (pm != null)
286            SeamInitializer.instance().setTidePersistenceManager(pm);
287    }
288    
289        // Needed for Seam 2.1
290    public boolean isInterceptorEnabled() {
291        return true;
292    }
293}