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