package org.jboss.seam.wicket;
import static org.jboss.seam.ScopeType.STATELESS;
import static org.jboss.seam.ScopeType.UNSPECIFIED;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.wicket.Page;
import org.apache.wicket.util.string.Strings;
import org.jboss.seam.Component;
import org.jboss.seam.Namespace;
import org.jboss.seam.RequiredException;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.Conversational;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.RaiseEvent;
import org.jboss.seam.annotations.bpm.BeginTask;
import org.jboss.seam.annotations.bpm.EndTask;
import org.jboss.seam.annotations.bpm.StartTask;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.annotations.security.RoleCheck;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Init;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.security.Identity;
import org.jboss.seam.wicket.annotations.NoConversationPage;
import org.jboss.seam.wicket.ioc.BijectedAttribute;
import org.jboss.seam.wicket.ioc.BijectedField;
import org.jboss.seam.wicket.ioc.BijectedMethod;
import org.jboss.seam.wicket.ioc.BijectionInterceptor;
import org.jboss.seam.wicket.ioc.ConversationInterceptor;
import org.jboss.seam.wicket.ioc.EventInterceptor;
import org.jboss.seam.wicket.ioc.InjectedAttribute;
import org.jboss.seam.wicket.ioc.InjectedField;
import org.jboss.seam.wicket.ioc.StatelessInterceptor;
public class WicketComponent<T>
{
private final class InjectedLogger extends InjectedField<Logger>
{
private Log logInstance;
InjectedLogger(Field field, Logger annotation)
{
super(field, annotation);
String category = getAnnotation().value();
if ("".equals(category))
{
logInstance = Logging.getLog(getType());
}
else
{
logInstance = Logging.getLog(category);
}
}
Log getLogInstance()
{
return logInstance;
}
public void set(Object bean)
{
super.set(bean, logInstance);
}
}
private static LogProvider log = Logging.getLogProvider(WicketComponent.class);
private Class<? extends T> type;
private Class<?> enclosingType;
private String enclosingInstanceVariableName;
private List<BijectedAttribute<In>> inAttributes = new ArrayList<BijectedAttribute<In>>();
private List<BijectedAttribute<Out>> outAttributes = new ArrayList<BijectedAttribute<Out>>();
private List<InjectedLogger> loggerFields = new ArrayList<InjectedLogger>();
private Set<AccessibleObject> conversationManagementMembers = new HashSet<AccessibleObject>();
private List<StatelessInterceptor<T>> interceptors = new ArrayList<StatelessInterceptor<T>>();
private Set<String> restrictions;
boolean anyMethodHasRaiseEvent = false;
private Class<? extends Page> noConversationPage;
public Class<?> getType()
{
return type;
}
public static <T> WicketComponent<T> getInstance(Class<? extends T> type)
{
String name = getContextVariableName(type);
if (Contexts.getApplicationContext().isSet(name))
{
return (WicketComponent) Contexts.getApplicationContext().get(name);
}
else
{
return null;
}
}
private void initInterceptors()
{
// TODO Add a check to see whether we really need this
interceptors.add(new BijectionInterceptor());
if (!conversationManagementMembers.isEmpty())
{
interceptors.add(new ConversationInterceptor());
}
if (anyMethodHasRaiseEvent)
{
interceptors.add(new EventInterceptor());
}
}
public List<StatelessInterceptor<T>> getInterceptors()
{
return interceptors;
}
public static String getContextVariableName(Class<?> type)
{
return type.getName() + ".wicketComponent";
}
public String getName()
{
return getContextVariableName(type);
}
public WicketComponent(Class<? extends T> type)
{
this.type = type;
this.enclosingType = type.getEnclosingClass();
if (this.enclosingType != null)
{
log.debug("Class: " + type + ", enclosed by " + enclosingType);
}
else
{
log.debug("Class: " + type);
}
scan();
initInterceptors();
Contexts.getApplicationContext().set(getName(), this);
}
private void scan()
{
Class clazz = type;
scanClassEnclosureHierachy();
while (clazz != Object.class) {
for (Method method : clazz.getDeclaredMethods())
{
add(method);
}
for (Field field : clazz.getDeclaredFields())
{
add(field);
}
for(Constructor<T> constructor : clazz.getDeclaredConstructors())
{
add(constructor);
}
clazz = clazz.getSuperclass();
}
}
private void scanClassEnclosureHierachy()
{
Class cls = type;
int i = 0;
while (cls != null)
{
for (Annotation annotation : cls.getAnnotations())
{
if (annotation instanceof Restrict)
{
Restrict restrict = (Restrict) annotation;
if (restrictions == null) restrictions = new HashSet<String>();
if ( Strings.isEmpty(restrict.value()) )
{
throw new IllegalStateException("@Restrict on " + cls.getName() + " must specify an expression");
}
restrictions.add(restrict.value());
}
if (annotation.annotationType().isAnnotationPresent(RoleCheck.class))
{
if (restrictions == null) restrictions = new HashSet<String>();
restrictions.add("#{identity.hasRole('" +
annotation.annotationType().getSimpleName().toLowerCase() + "')}");
}
if (annotation instanceof NoConversationPage)
{
NoConversationPage noConversationPage = (NoConversationPage) annotation;
this.noConversationPage = noConversationPage.value();
}
}
cls = cls.getEnclosingClass();
i++;
}
if (i > 1)
{
this.enclosingInstanceVariableName = "this$" + (i - 2);
}
}
public void checkRestrictions()
{
if (Identity.isSecurityEnabled() && restrictions != null)
{
for (String restriction : restrictions)
{
Identity.instance().checkRestriction(restriction);
}
}
}
public void outject(T target)
{
for (BijectedAttribute<Out> out : outAttributes)
{
Object value = out.get(target);
if (value==null && out.getAnnotation().required())
{
throw new RequiredException("@Out attribute requires non-null value: " + out.toString());
}
else
{
Component component = null;
if (out.getAnnotation().scope()==UNSPECIFIED)
{
component = Component.forName(out.getContextVariableName());
if (value!=null && component!=null)
{
if (!component.isInstance(value))
{
throw new IllegalArgumentException("attempted to bind an @Out attribute of the wrong type to: " + out.toString());
}
}
}
else if (out.getAnnotation().scope()==STATELESS)
{
throw new IllegalArgumentException("cannot specify explicit scope=STATELESS on @Out: " + out.toString());
}
ScopeType outScope = component == null ? out.getAnnotation().scope() : component.getScope();
if (outScope == null)
{
throw new IllegalArgumentException("cannot determine scope to outject to on @Out: " + out.toString());
}
if (outScope.isContextActive())
{
if (value==null)
{
outScope.getContext().remove(out.getContextVariableName());
}
else
{
outScope.getContext().set(out.getContextVariableName(), value);
}
}
}
}
}
public void disinject(T target)
{
for ( InjectedAttribute<In> in : inAttributes )
{
if ( !in.getType().isPrimitive() )
{
in.set(target, null);
}
}
}
public void inject(T instance)
{
for ( BijectedAttribute<In> in : inAttributes )
{
in.set( instance, getValue(in, instance) );
}
}
private void add(Constructor<T> constructor)
{
if ( constructor.isAnnotationPresent(Begin.class) ||
constructor.isAnnotationPresent(org.jboss.seam.wicket.annotations.Begin.class) ||
constructor.isAnnotationPresent(End.class) ||
constructor.isAnnotationPresent(StartTask.class) ||
constructor.isAnnotationPresent(BeginTask.class) ||
constructor.isAnnotationPresent(EndTask.class) )
{
conversationManagementMembers.add(constructor);
}
}
private void add(Method method)
{
if ( method.isAnnotationPresent(In.class) )
{
final In in = method.getAnnotation(In.class);
inAttributes.add( new BijectedMethod(method, in)
{
@Override
protected String getSpecifiedContextVariableName()
{
return in.value();
}
});
}
if ( method.isAnnotationPresent(Out.class) )
{
final Out out = method.getAnnotation(Out.class);
outAttributes.add( new BijectedMethod(method, out)
{
@Override
protected String getSpecifiedContextVariableName()
{
return out.value();
}
});
}
if ( method.isAnnotationPresent(Begin.class) ||
method.isAnnotationPresent(org.jboss.seam.wicket.annotations.Begin.class) ||
method.isAnnotationPresent(End.class) ||
method.isAnnotationPresent(StartTask.class) ||
method.isAnnotationPresent(BeginTask.class) ||
method.isAnnotationPresent(EndTask.class) ||
method.isAnnotationPresent(Conversational.class))
{
conversationManagementMembers.add(method);
}
if (method.isAnnotationPresent(RaiseEvent.class))
{
anyMethodHasRaiseEvent = true;
}
}
private void add(Field field)
{
if ( field.isAnnotationPresent(In.class) )
{
final In in = field.getAnnotation(In.class);
inAttributes.add( new BijectedField(field, in)
{
@Override
protected String getSpecifiedContextVariableName()
{
return in.value();
}
});
}
if ( field.isAnnotationPresent(Out.class) )
{
final Out out = field.getAnnotation(Out.class);
outAttributes.add( new BijectedField(field, out)
{
@Override
protected String getSpecifiedContextVariableName()
{
return out.value();
}
});
}
if ( field.isAnnotationPresent(Logger.class) )
{
final Logger logger = field.getAnnotation(Logger.class);
InjectedLogger loggerField = new InjectedLogger(field, logger);
if ( Modifier.isStatic( field.getModifiers() ) )
{
loggerField.set(null);
}
else
{
loggerFields.add(loggerField);
}
}
}
private Object getValue(BijectedAttribute<In> in, Object bean)
{
Object result;
String name = in.getContextVariableName();
if ( name.startsWith("#") )
{
if ( log.isDebugEnabled() )
{
log.trace("trying to inject with EL expression: " + name);
}
result = Expressions.instance().createValueExpression(name).getValue();
}
else if ( in.getAnnotation().scope()==UNSPECIFIED )
{
if ( log.isDebugEnabled() )
{
log.trace("trying to inject with hierarchical context search: " + name);
}
boolean create = in.getAnnotation().create() && !org.jboss.seam.contexts.Lifecycle.isDestroying();
result = getInstanceInAllNamespaces(name, create);
}
else
{
if ( in.getAnnotation().create() )
{
throw new IllegalArgumentException(
"cannot combine create=true with explicit scope on @In: " +
in.toString()
);
}
if ( in.getAnnotation().scope()==STATELESS )
{
throw new IllegalArgumentException(
"cannot specify explicit scope=STATELESS on @In: " +
in.toString()
);
}
log.trace("trying to inject from specified context: " + name);
if ( in.getAnnotation().scope().isContextActive() )
{
result = in.getAnnotation().scope().getContext().get(name);
}
else
{
result = null;
}
}
if ( result==null && in.getAnnotation().required() )
{
throw new RequiredException( "@In attribute requires non-null value: " + type + '.' + name );
}
else
{
return result;
}
}
private static Object getInstanceInAllNamespaces(String name, boolean create)
{
Object result;
result = Component.getInstance(name, create);
if (result==null)
{
for ( Namespace namespace: Init.instance().getGlobalImports() )
{
result = namespace.getComponentInstance(name, create);
if (result!=null) break;
}
}
return result;
}
public void initialize(T bean)
{
injectLog(bean);
}
private void injectLog(T bean)
{
for (InjectedLogger injectedLogger : loggerFields)
{
injectedLogger.set(bean);
}
}
public Class<?> getEnclosingType()
{
return enclosingType;
}
public String getEnclosingInstanceVariableName()
{
return enclosingInstanceVariableName;
}
@Override
public String toString()
{
return "WicketComponent(" + type + ")";
}
public boolean isConversationManagementMethod(AccessibleObject member)
{
return member!=null &&
conversationManagementMembers.contains(member);
}
public Class<? extends Page> getNoConversationPage()
{
return noConversationPage;
}
}