package org.nocket.gen.page;
import gengui.annotations.Group;
import gengui.domain.DomainObjectReference;
import gengui.guiadapter.ElementNotFoundException;
import gengui.util.ReflectionUtil;
import gengui.util.SevereGUIException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.MarkupContainer;
import org.nocket.NocketSession;
import org.nocket.gen.domain.element.DomainElementI;
import org.nocket.gen.page.inject.PageComponentInjection;
import org.nocket.gen.page.visitor.GeneratedBindingVisitor;
import org.nocket.gen.page.visitor.PageElementVisitorI;
import org.nocket.gen.page.visitor.bind.builder.BindingInterceptor;
public class GeneratedBinding {
private transient DMDWebGenPageContext context;
private transient MarkupContainer page;
private transient boolean rebound;
private List<BindingInterceptor> interceptors = new ArrayList<BindingInterceptor>();
private List<PageElementVisitorI> additionalPageVisitors = new ArrayList<PageElementVisitorI>();
/**
* TODO JL: Using this cache is still experimental as there are some open
* issues about it. It would be the fastest way of setting up a new page's
* binding and it is based on the fact that all internal data structures
* referred by a DMDWebGenPageContext (components, models, registries, etc)
* can completely be reused. Any access to the underlying domain POJO is
* based on the persistent relation between the DMDWebGenPageContext and the
* page's default model object (which is the POJO). All other relations are
* transient and refer to this one and only persistent relationship. See the
* UML model for the internal structures as a prove. However, the ere are
* the following issues to complete:
* <ul>
* <li>When reinitializing the {@link DMDWebGenPageContext} with the new
* page (see method {@link DMDWebGenPageContext#rebind(MarkupContainer)}),
* any existing transient relationships in all dependent objects must
* somehow be reseted.
* <li>In case of a repeating panel element, we need multiple bindings for
* the same type at the same time. In this case the
* {@link DMDWebGenPageContext} must somehow be cloned. Ini particular we
* must find out if cloning is necessary. That's quite the same challenge as
* in gengui's concept of reconnection and replication.
* <ul>
* As the context caching is still in an experimental state, the caching is
* by default switched off, i.e. the context map in here is empty.
*/
protected static final Map<String, DMDWebGenPageContext> contextCache = new HashMap<String, DMDWebGenPageContext>();
public GeneratedBinding(MarkupContainer page, boolean allowCaching) {
this.page = page;
if (!allowCaching) {
this.context = new DMDWebGenPageContext(page);
}
else {
synchronized (GeneratedBinding.class) {
String pageClassName = page.getClass().getName();
this.context = contextCache.get(pageClassName);
if (context == null) {
this.context = new DMDWebGenPageContext(page);
contextCache.put(pageClassName, context);
}
else {
rebound = true;
}
}
}
}
public GeneratedBinding(MarkupContainer page) {
this(page, false);
}
public DMDWebGenPageContext getContext() {
return context;
}
public GeneratedBinding withVisitors(PageElementVisitorI... visitors) {
return withVisitors(Arrays.asList(visitors));
}
public GeneratedBinding withVisitors(List<PageElementVisitorI> visitors) {
this.additionalPageVisitors.addAll(visitors);
return this;
}
/**
* Interceptors will be called in the order in which they have been added.
* The first one to provide a component instead of null will get used.
*
* As a fallback when no interceptor provided a component, the default
* binding is used then.
*/
public GeneratedBinding withInterceptors(BindingInterceptor... interceptors) {
return withInterceptors(Arrays.asList(interceptors));
}
/**
* Interceptors will be called in the order in which they have been added.
* The first one to provide a component instead of null will get used.
*
* As a fallback when no interceptor provided a component, the default
* binding is used then.
*/
public GeneratedBinding withInterceptors(List<BindingInterceptor> interceptors) {
this.interceptors.addAll(interceptors);
return this;
}
/**
* Interceptors will be called in the order in which they have been added.
* The first one to provide a component instead of null will get used.
*
* As a fallback when no interceptor provided a component, the default
* binding is used then.
*/
public List<BindingInterceptor> getInterceptors() {
return interceptors;
}
public void bind() {
if (!rebound) {
GeneratedBindingVisitor visitor = new GeneratedBindingVisitor(this, additionalPageVisitors.toArray(new PageElementVisitorI[0]));
try {
new PageProcessor(visitor).process();
} catch (ElementNotFoundException enfx1) {
if (!getContext().acceptMissingElements() && !checkMethodAndLookForGlobalAnnotation(enfx1.getMessage())) {
if (getContext().mergeHtmlFileOnTheFly()) {
context.clearPage();
this.context = new DMDWebGenPageContext(page);
visitor = new GeneratedBindingVisitor(this);
try {
new PageProcessor(visitor).process();
} catch (ElementNotFoundException enfx2) {
throw new SevereGUIException(enfx2);
}
}
else {
throw new SevereGUIException(enfx1);
}
}
}
}
else {
this.getContext().rebind(page);
}
new PageComponentInjection(context).inject();
NocketSession.get().getDMDWebGenGuiServiceProvider().onGeneratedBinding(context);
}
/**
* Check if behind this wicketid is a method with an group annotation. The
* whole path will be analysed.
*
* @param wicketId
* @return true if it is a annotated method behind this wicket:id
*/
private boolean checkMethodAndLookForGlobalAnnotation(String wicketId) {
DomainElementI<DomainObjectReference> element = context.getDomainRegistry().getElement(wicketId);
if (element == null) {
return false;
}
if (element.getMethod().getAnnotation(Group.class) != null) {
return true;
}
Class<?> clazz = context.getPage().getDefaultModelObject().getClass();
if (checkForGlobalAnnotationInPath(wicketId, clazz)) {
return true;
}
String name = context.getPage().getClass().getSimpleName();
int index = name.indexOf("_");
if (index > -1 && name.endsWith("Panel")) {
String panelGetterName = StringUtils.substring(name, index + 1, -5);
if (StringUtils.isNotBlank(panelGetterName) && checkForGlobalAnnotationInObject(panelGetterName, clazz)) {
return true;
}
}
return false;
}
private boolean checkForGlobalAnnotationInObject(String panelGetterName, Class<?> clazz) {
Method[] methods = clazz.getMethods();
// Determing the name of the panel class during the generation process, the value of the group annotation will be used.
// If the value contains '.' they have to be replaced by '_'.
// For a comparison it is necessary to withdraw this change
panelGetterName = panelGetterName.replace("_", ".");
for (Method method : methods) {
Group groupAnnotation = method.getAnnotation(Group.class);
if (groupAnnotation != null && StringUtils.equalsIgnoreCase(panelGetterName, groupAnnotation.value())) {
return true;
}
}
return false;
}
protected boolean checkForGlobalAnnotationInPath(String wicketId, Class<?> clazz) {
String[] parts = StringUtils.split(wicketId, ".");
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
Method method = ReflectionUtil.findGetterMethod(clazz, part);
if (method == null) {
// if there is no getter, check a button method
method = ReflectionUtil.findMethod(clazz, part);
}
if (method == null) {
// if there is no method, there will be no group annotation
return false;
} else if (method.getAnnotation(Group.class) != null) {
return true;
}
// check the return type for global annotation
clazz = method.getReturnType();
}
return false;
}
}