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