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    
021    package org.granite.tide.seam;
022    
023    import java.lang.annotation.Annotation;
024    import java.lang.reflect.Field;
025    import java.util.ArrayList;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    
030    import org.granite.tide.TidePersistenceManager;
031    import org.granite.tide.annotations.BypassTideInterceptor;
032    import org.granite.tide.invocation.ContextUpdate;
033    import org.granite.tide.seam.TideInit.FactoryVariable;
034    import org.granite.tide.seam.lazy.SeamInitializer;
035    import org.granite.tide.seam.lazy.TidePersistenceFactory;
036    import org.jboss.seam.Component;
037    import org.jboss.seam.ScopeType;
038    import org.jboss.seam.Component.BijectedAttribute;
039    import org.jboss.seam.annotations.DataBinderClass;
040    import org.jboss.seam.annotations.In;
041    import org.jboss.seam.annotations.Out;
042    import org.jboss.seam.annotations.intercept.AroundInvoke;
043    import org.jboss.seam.annotations.intercept.Interceptor;
044    import org.jboss.seam.annotations.security.Restrict;
045    import org.jboss.seam.bpm.BusinessProcessInterceptor;
046    import org.jboss.seam.contexts.Context;
047    import org.jboss.seam.contexts.Contexts;
048    import org.jboss.seam.core.BijectionInterceptor;
049    import org.jboss.seam.core.EventInterceptor;
050    import org.jboss.seam.databinding.DataBinder;
051    import org.jboss.seam.intercept.AbstractInterceptor;
052    import org.jboss.seam.intercept.InvocationContext;
053    import org.jboss.seam.log.LogProvider;
054    import 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})
072    public 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    }