/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.tide.seam;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.granite.tide.TidePersistenceManager;
import org.granite.tide.annotations.BypassTideInterceptor;
import org.granite.tide.invocation.ContextUpdate;
import org.granite.tide.seam.TideInit.FactoryVariable;
import org.granite.tide.seam.lazy.SeamInitializer;
import org.granite.tide.seam.lazy.TidePersistenceFactory;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.Component.BijectedAttribute;
import org.jboss.seam.annotations.DataBinderClass;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.Interceptor;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.bpm.BusinessProcessInterceptor;
import org.jboss.seam.contexts.Context;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.core.BijectionInterceptor;
import org.jboss.seam.core.EventInterceptor;
import org.jboss.seam.databinding.DataBinder;
import org.jboss.seam.intercept.AbstractInterceptor;
import org.jboss.seam.intercept.InvocationContext;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
/**
* This interceptor has 4 activities :
* - Updating the context with data received from the Flex client, remerging client data in the persistence context when needed
* - Intercept outjected values to return it to the client
* - Determine the Persistence Context being used for the conversation and creating a lazyinitializer
* storing it in the current conversation
* - Return all changed values to the client
*
* @author Venkat DANDA
* @author Cameron INGRAM
* @author William DRAI
*/
@Interceptor(around={BijectionInterceptor.class, EventInterceptor.class, BusinessProcessInterceptor.class})
public class TideInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 1L;
private static final LogProvider log = Logging.getLogProvider(TideInterceptor.class);
private boolean reentrant; //OK, since all Seam components are single-threaded
@AroundInvoke
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object aroundInvoke(InvocationContext invocation) throws Exception {
if (reentrant) {
log.trace("About to invoke method");
if (log.isTraceEnabled())
log.trace("reentrant call to component: " + getComponent().getName() );
Object result = invocation.proceed();
log.trace("Method invoked");
return result;
}
reentrant = true;
try {
if (getComponent().getBeanClass().isAnnotationPresent(BypassTideInterceptor.class))
return invocation.proceed();
TideInvocation tideInvocation = TideInvocation.get();
if (tideInvocation == null || tideInvocation.isLocked())
return invocation.proceed();
AbstractSeamServiceContext tideContext = null;
if (Contexts.isSessionContextActive())
tideContext = (AbstractSeamServiceContext)Component.getInstance(AbstractSeamServiceContext.COMPONENT_NAME, true);
if (tideContext == null)
return invocation.proceed();
// Ignore lifecycle methods
if (SeamUtils.isLifecycleMethod(getComponent(), invocation.getMethod())) {
tideInvocation.lock();
Object result = invocation.proceed();
tideInvocation.unlock();
return result;
}
boolean evaluate = false;
//Check for persistence
checkForPersistenceContexts(invocation);
// Ignore inner interceptions of other components during processing
if (tideInvocation.isEnabled() && !tideInvocation.isUpdated()) {
List<ContextUpdate> updates = new ArrayList<ContextUpdate>(tideInvocation.getUpdates());
tideInvocation.updated();
tideContext.restoreContext(updates, getComponent(), invocation.getTarget());
evaluate = true;
// Inject DataModel selections
Field field = getComponent().getClass().getDeclaredField("dataModelGetters");
field.setAccessible(true);
List<BijectedAttribute<?>> dataModelGetters = (List<BijectedAttribute<?>>)field.get(getComponent());
for (BijectedAttribute<?> getter : dataModelGetters) {
Annotation dataModelAnn = getter.getAnnotation();
DataBinder wrapper = dataModelAnn.annotationType().getAnnotation(DataBinderClass.class).value().newInstance();
String name = getter.getName();
ScopeType scope = wrapper.getVariableScope(dataModelAnn);
if (scope == ScopeType.UNSPECIFIED) {
scope = getComponent().getScope();
if (scope == ScopeType.STATELESS)
scope = ScopeType.EVENT;
}
Object dataModel = scope.getContext().get(name);
if (dataModel != null && dataModel instanceof TideDataModel) {
Field field2 = getComponent().getClass().getDeclaredField("dataModelSelectionSetters");
field2.setAccessible(true);
Map<String, BijectedAttribute<?>> setters = (Map<String, BijectedAttribute<?>>)field2.get(getComponent());
BijectedAttribute setter = setters.get(name);
if (setter != null) {
Object value = setter.get(invocation.getTarget());
((TideDataModel)dataModel).setRowData(value);
}
}
}
}
// Do invocation
Object result = invocation.proceed();
boolean restrict = getComponent().beanClassHasAnnotation(Restrict.class);
// Intercept outjected values
if (getComponent().needsOutjection()) {
List<BijectedAttribute<Out>> li = getComponent().getOutAttributes();
for (BijectedAttribute<Out> att: li) {
ScopeType scope = att.getAnnotation().scope();
if (ScopeType.UNSPECIFIED.equals(scope)) {
Component outComponent = Component.forName(att.getName());
if (outComponent != null)
scope = outComponent.getScope();
else
scope = getComponent().getScope();
}
if (ScopeType.STATELESS.equals(scope))
scope = ScopeType.EVENT;
if (!(ScopeType.EVENT.equals(scope))) {
Context context = Contexts.getEventContext();
if (context.get(att.getName() + "_tide_unspecified_") != null) {
context.remove(att.getName() + "_tide_unspecified_");
context.remove(att.getName());
}
}
tideContext.addResultEval(new ScopedContextResult(att.getName(), null, scope, restrict));
}
Field field = getComponent().getClass().getDeclaredField("dataModelGetters");
field.setAccessible(true);
List<BijectedAttribute<?>> dataModelGetters = (List<BijectedAttribute<?>>)field.get(getComponent());
for (BijectedAttribute<?> getter : dataModelGetters) {
Annotation anno = getter.getAnnotation();
DataBinder wrapper = anno.annotationType().getAnnotation(DataBinderClass.class).value().newInstance();
ScopeType scope = wrapper.getVariableScope(anno);
if (ScopeType.UNSPECIFIED.equals(scope))
scope = getComponent().getScope();
if (ScopeType.STATELESS.equals(scope))
scope = ScopeType.EVENT;
if (!(ScopeType.EVENT.equals(scope))) {
Context context = Contexts.getEventContext();
if (context.get(getter.getName() + "_tide_unspecified_") != null) {
context.remove(getter.getName() + "_tide_unspecified_");
context.remove(getter.getName());
}
}
tideContext.addResultEval(new ScopedContextResult(getter.getName(), null, scope, restrict));
}
}
// Force evaluation of factory components dependent on the called component
Set<FactoryVariable> factoredVariables = TideInit.getFactoredVariables(getComponent());
if (factoredVariables != null) {
for (FactoryVariable variable : factoredVariables) {
ScopeType scope = variable.getScope();
if (ScopeType.UNSPECIFIED.equals(scope))
scope = getComponent().getScope();
if (ScopeType.STATELESS.equals(scope))
scope = ScopeType.EVENT;
tideContext.addResultEval(new ScopedContextResult(variable.getVariableName(), null, scope, restrict));
}
}
if (evaluate)
tideInvocation.evaluated(tideContext.evaluateResults(getComponent(), invocation.getTarget(), false));
return result;
}
finally {
reentrant = false;
}
}
/**
* Try to determine what the PersistenceContext is and create an appropriate
* lazy initializer for it.
* @param ctx the bean bieng accessed.
*/
private void checkForPersistenceContexts(InvocationContext ctx) {
Object bean = ctx.getTarget();
TidePersistenceManager pm = null;
for ( BijectedAttribute<?> ba: getComponent().getPersistenceContextAttributes() ) {
Object object = ba.get(bean);
SeamInitializer.instance().setTidePersistenceManager(TidePersistenceFactory.createTidePersistence(getComponent(), object));
return;
}
if (getComponent().needsInjection()) {
List<BijectedAttribute<In>> li = getComponent().getInAttributes();
for (BijectedAttribute<In> att: li) {
try {
pm = TidePersistenceFactory.createTidePersistence(getComponent(), att);
} catch (RuntimeException ex) {
continue;
}
if (pm != null) {
SeamInitializer.instance().setTidePersistenceManager(pm);
return;
}
}
}
//Last chance to see a PersistenceManager can be found for this invocation
pm = TidePersistenceFactory.createTidePersistence(getComponent(), ctx.getTarget());
if (pm != null)
SeamInitializer.instance().setTidePersistenceManager(pm);
}
// Needed for Seam 2.1
public boolean isInterceptorEnabled() {
return true;
}
}