package org.nocket.gen.page;
import gengui.domain.DomainObjectReference;
import gengui.guiadapter.ElementNotFoundException;
import gengui.util.DomainProperties.JfdRetentionStrategy;
import gengui.util.SevereGUIException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.nocket.gen.domain.DMDWebGenContext;
import org.nocket.gen.domain.DomainProcessor;
import org.nocket.gen.domain.HTMLDocumentCachingPolicy;
import org.nocket.gen.domain.NocketGenerator;
import org.nocket.gen.domain.WebDomainProperties;
import org.nocket.gen.domain.ref.DomainObjectReferenceFactory;
import org.nocket.gen.domain.visitor.registry.DomainRegistry;
import org.nocket.gen.domain.visitor.registry.DomainRegistryVisitor;
import org.nocket.gen.page.element.ModalElement;
import org.nocket.gen.page.element.synchronizer.SynchronizerHelper;
import org.nocket.gen.page.element.synchronizer.error.AjaxAlertMethodExceptionHandler;
import org.nocket.gen.page.element.synchronizer.error.MethodExceptionHandlerI;
import org.nocket.gen.page.guiservice.TouchedRegistry;
import org.nocket.gen.page.guiservice.TouchedRegistryData;
import org.nocket.gen.page.visitor.bind.ComponentRegistry;
import org.nocket.gen.page.visitor.bind.builder.BindingInterceptor;
import org.nocket.gen.page.visitor.registry.PageRegistry;
import org.nocket.gen.page.visitor.registry.PageRegistryVisitor;
import org.nocket.gen.resources.DMDCachingResourceStreamLocator;
import org.nocket.page.DMDWebPage;
public class DMDWebGenPageContext implements Serializable {
public static final MetaDataKey<DMDWebGenPageContext> CONTEXT_KEY = new MetaDataKey<DMDWebGenPageContext>() {
private static final long serialVersionUID = 1L;
};
private static MethodExceptionHandlerI defaultMethodExceptionHandler;
private static final List<BindingInterceptor> defaultBindingInterceptors = new ArrayList<BindingInterceptor>();
private static final Map<String, Document> documentCache = new HashMap<String, Document>();
private static final Map<String, DomainRegistry<DomainObjectReference>> domainRegistryPrototypes = new HashMap<String, DomainRegistry<DomainObjectReference>>();
private final TouchedRegistryData touchedRegistryData;
private final TouchedRegistry touchedRegistry;
private MethodExceptionHandlerI methodExceptionHandler;
private transient Boolean pageUpdated;
private MarkupContainer page;
private transient DomainObjectReferenceFactory refFactory;
private transient DomainRegistry<DomainObjectReference> domainRegistry;
private transient PageRegistry pageRegistry;
private transient ComponentRegistry componentRegistry;
private transient Document htmlDocument;
private transient List<Component> inheritedChildren = new ArrayList<Component>();
private transient WebDomainProperties configuration;
private transient Behavior resetAfterRenderBehaviour = new Behavior() {
@Override
public void afterRender(Component component) {
super.afterRender(component);
//domainreference needs to be reset on ajax updates
refFactory = null;
domainRegistry = null;
}
};
static {
defaultMethodExceptionHandler = new AjaxAlertMethodExceptionHandler();
}
public DMDWebGenPageContext(MarkupContainer page) {
this.page = page;
if (org.nocket.page.DMDWebPage.class.isAssignableFrom(page.getClass())) {
((DMDWebPage) page).setPagecontext(this);
}
page.add(resetAfterRenderBehaviour);
this.pageUpdated = true;
this.componentRegistry = new ComponentRegistry();
// initial pageRegistry gets filled during first bind run
this.pageRegistry = new PageRegistry();
this.touchedRegistryData = new TouchedRegistryData();
this.touchedRegistry = new TouchedRegistry(this);
this.page.setMetaData(CONTEXT_KEY, this);
registerInheritedChildren();
}
protected void registerInheritedChildren() {
page.visitChildren(new IVisitor<Component, Object>() {
@Override
public void component(Component child, IVisit<Object> visit) {
if (child.getParent() == page) {
inheritedChildren.add(child);
}
}
});
}
public void updatePage(AjaxRequestTarget target) {
MarkupContainer root = SynchronizerHelper.findRoot(target.getPage());
IVisitor<Component, Object> visitor = new IVisitor<Component, Object>() {
@Override
public void component(Component object, IVisit<Object> visit) {
if (object instanceof MarkupContainer && object.getClass().equals(page.getClass())
&& StringUtils.equals(page.getId(), object.getId())) {
DMDWebGenPageContext.this.page = (MarkupContainer) object;
pageUpdated = true;
}
}
};
visitor.component(root, null);
root.visitChildren(visitor);
if (pageUpdated == null) {
pageUpdated = false;
}
}
public static void setDefaultMethodExceptionHandler(MethodExceptionHandlerI defaultMethodExceptionHandler) {
DMDWebGenPageContext.defaultMethodExceptionHandler = defaultMethodExceptionHandler;
}
public static List<BindingInterceptor> getDefaultBindingInterceptors() {
return defaultBindingInterceptors;
}
public MethodExceptionHandlerI getMethodExceptionHandler() {
if (methodExceptionHandler != null) {
return methodExceptionHandler;
} else {
return defaultMethodExceptionHandler;
}
}
public void setMethodExceptionHandler(MethodExceptionHandlerI methodExceptionHandler) {
this.methodExceptionHandler = methodExceptionHandler;
}
private DomainObjectReferenceFactory loadRefFactory() {
return new DomainObjectReferenceFactory(getPage().getDefaultModelObject());
}
private DomainRegistry<DomainObjectReference> loadDomainRegistry() {
// Wicket's development mode allows to reload classes at runtime, so in that mode
// we must not cache any data structures which depend on class structures. The
// domain registry prototypes are such a candidate.
if (WebApplication.get().getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT)
return constructDomainRegistry();
synchronized (DMDWebGenPageContext.class) {
String domainClassName = page.getDefaultModelObject().getClass().getName();
DomainRegistry<DomainObjectReference> registry = domainRegistryPrototypes.get(domainClassName);
if (registry == null) {
registry = constructDomainRegistry();
domainRegistryPrototypes.put(domainClassName, registry);
return registry;
} else {
// Construct a new DomainRegistry from an existing prototype rather than from
// traversing the domain class structure. Creation from prototype is much faster.
return registry.replicate(page.getDefaultModelObject());
}
}
}
protected DomainRegistry<DomainObjectReference> constructDomainRegistry() {
DomainRegistry<DomainObjectReference> registry;
DMDWebGenContext<DomainObjectReference> ctx = new DMDWebGenContext<DomainObjectReference>(null, null,
null, getRefFactory());
DomainRegistryVisitor<DomainObjectReference> visitor = new DomainRegistryVisitor<DomainObjectReference>(
ctx);
new DomainProcessor<DomainObjectReference>(ctx, visitor).process();
registry = visitor.getDomainRegistry();
return registry;
}
private Document loadDocument() {
synchronized (DMDWebGenPageContext.class) {
String pagePath = htmlPath(getPage().getClass());
WebDomainProperties props = getConfiguration();
HTMLDocumentCachingPolicy cachingPolicy = props.getHTMLDocumentCachingPolicy();
Document document = readCachedDocument(getPage().getClass(), props);
if (document == null) {
IResourceStream rstream = getInputStreamForHtmlFile(getPage().getClass(), props);
InputStream in = openResourceStream(rstream);
if (in == null) {
in = createHtmlFileOnTheFly(props);
}
try {
String html = IOUtils.toString(in);
document = Jsoup.parse(html);
if (cachingPolicy != HTMLDocumentCachingPolicy.none)
documentCache.put(pagePath, document);
} catch (IOException e) {
throw new SevereGUIException(e);
} finally {
IOUtils.closeQuietly(in);
}
}
return document;
}
}
public WebDomainProperties getConfiguration() {
synchronized (DMDWebGenPageContext.class) {
if (configuration == null)
configuration = new WebDomainProperties(getRefFactory().getRootReference().getClassRef());
}
return configuration;
}
protected Document readCachedDocument(Class<? extends MarkupContainer> pageClass, WebDomainProperties props) {
String pagePath = htmlPath(pageClass);
HTMLDocumentCachingPolicy cachingPolicy = props.getHTMLDocumentCachingPolicy();
switch (cachingPolicy) {
case none:
return null;
case age:
IResourceStream rstream = getInputStreamForHtmlFile(pageClass, props);
if (rstream == null)
return null;
//TODO JL: HERE WE ARE!!!!!!!!!!!!!!!!!
// Fall through
default:
return documentCache.get(pagePath);
}
}
protected InputStream createHtmlFileOnTheFly(WebDomainProperties props) {
IResourceStreamLocator locator = Application.get().getResourceSettings().getResourceStreamLocator();
// Without a DMDCachinResourceStreamLocator we could create the HTML but we could
// not force a re-allocation of the created file.
if (locator instanceof DMDCachingResourceStreamLocator) {
DMDCachingResourceStreamLocator dmdLocator = (DMDCachingResourceStreamLocator) locator;
Class<? extends MarkupContainer> pageClass = getPage().getClass();
dmdLocator.drop(pageClass, htmlLookupPaths(pageClass));
JfdRetentionStrategy strategy = props.getJFDRetentionStrategy();
if (strategy != JfdRetentionStrategy.none) {
runHtmlGenerator(props);
IResourceStream rstream = getInputStreamForHtmlFile(pageClass, props);
InputStream in = openResourceStream(rstream);
if (in != null)
return in;
}
}
throw new SevereGUIException("No HTML file found for page " + getPage().getClass());
}
public boolean mergeHtmlFileOnTheFly() {
WebDomainProperties props = getConfiguration();
if (props.getJFDRetentionStrategy() != JfdRetentionStrategy.merge &&
props.getJFDRetentionStrategy() != JfdRetentionStrategy.silentmerge)
return false;
runHtmlGenerator(props);
return true;
}
protected void runHtmlGenerator(WebDomainProperties props) {
Class<? extends MarkupContainer> pageClass = getPage().getClass();
Class<?> domainClass = getRefFactory().getRootReference().getClassRef().getDomainClass();
boolean generatePanel = props.getHTMLPanelBaseClass().isAssignableFrom(pageClass);
new NocketGenerator().generateHTML(domainClass, generatePanel, null, null, null);
}
protected InputStream openResourceStream(IResourceStream rstream) {
if (rstream == null)
return null;
try {
return rstream.getInputStream();
} catch (ResourceStreamNotFoundException rsnfx) {
return null;
}
}
@SuppressWarnings("unchecked")
protected IResourceStream getInputStreamForHtmlFile(Class<? extends MarkupContainer> pageClass,
WebDomainProperties props) {
IResourceStream rstream = null;
IResourceStreamLocator resourceStreamLocator = Application.get().getResourceSettings()
.getResourceStreamLocator();
for (String htmlLookupPath : htmlLookupPaths(pageClass)) {
rstream = fetchStream(pageClass, htmlLookupPath, resourceStreamLocator);
if (rstream != null)
break;
}
if (rstream == null) {
Class<?> superclass = pageClass.getSuperclass();
if (superclass != props.getHTMLPageBaseClass() &&
MarkupContainer.class.isAssignableFrom(superclass)) {
rstream = getInputStreamForHtmlFile((Class<? extends MarkupContainer>) superclass, props);
}
}
return rstream;
}
private String htmlPath(Class<? extends MarkupContainer> pageClass) {
return pageClass.getName().replace(".", "/") + ".html";
}
private String[] htmlLookupPaths(Class<? extends MarkupContainer> pageClass) {
String htmlPath = htmlPath(pageClass);
return new String[] { htmlPath, "/" + htmlPath };
}
private IResourceStream fetchStream(Class<? extends MarkupContainer> clazz, String pagePath,
IResourceStreamLocator resourceStreamLocator) {
return resourceStreamLocator.locate(clazz, pagePath);
}
public MarkupContainer getPage() {
if (pageUpdated == null || pageUpdated == false) {
AjaxRequestTarget target = RequestCycle.get().find(AjaxRequestTarget.class);
if (target != null) {
updatePage(target);
}
}
return page;
}
public DomainObjectReferenceFactory getRefFactory() {
if (refFactory == null) {
refFactory = loadRefFactory();
}
return refFactory;
}
public DomainRegistry<DomainObjectReference> getDomainRegistry() {
if (domainRegistry == null) {
domainRegistry = loadDomainRegistry();
}
return domainRegistry;
}
public PageRegistry getPageRegistry() {
if (pageRegistry == null) {
pageRegistry = new PageRegistry();
// page registry has to be refilled again since it was discarded in
// the meantime
try {
new PageProcessor(new PageRegistryVisitor(this)).process();
} catch (ElementNotFoundException enfx) {
throw new SevereGUIException(enfx);
}
}
return pageRegistry;
}
public static ComponentRegistry getComponentRegistry(MarkupContainer page) {
final ComponentRegistry result = new ComponentRegistry();
MarkupContainer root = SynchronizerHelper.findRoot(page);
root.visitChildren(new IVisitor<Component, Object>() {
@Override
public void component(Component object, IVisit<Object> visit) {
if (object.getId().equals(ModalElement.DEFAULT_WICKET_ID)) {
//use the very first instance of this component!
if (result.getComponent(object.getId()) == null) {
result.addComponent(object);
visit.stop();
}
}
}
});
page.visitChildren(new IVisitor<Component, Object>() {
@Override
public void component(Component object, IVisit<Object> visit) {
if (result.getComponent(object.getId()) == null) {
result.addComponent(object);
}
}
});
return result;
}
public ComponentRegistry getComponentRegistry() {
if (componentRegistry == null) {
componentRegistry = getComponentRegistry(getPage());
}
return componentRegistry;
}
public Document getHtmlDocument() {
if (htmlDocument == null) {
htmlDocument = loadDocument();
}
return htmlDocument;
}
public TouchedRegistryData getTouchedRegistryData() {
return touchedRegistryData;
}
public TouchedRegistry getTouchedRegistry() {
return touchedRegistry;
}
@Deprecated
/**
* This method is not really deprecated but the very opposite: it is not yet fully functional
*/
public void rebind(final MarkupContainer newPage) {
// Alle direkten Kinder übertragen, welche die Klasse *nicht* von der Basisklasse geerbt hat
page.visitChildren(new IVisitor<Component, Object>() {
@Override
public void component(Component child, IVisit<Object> visit) {
if (child.getParent() == page && !inheritedChildren.contains(child)) {
newPage.add(child);
}
}
});
this.page = newPage;
//TODO JL: There are probably some more things which need to be reseted
pageUpdated = true;
refFactory = null;
//TODO JL: What about the DomainRegistry? It looks to be an expensive thing to
// re-create as it traverses the class structure. This is not necessary most of the
// time as the class structure changes only rarely at runtime. Only in case Wicket
// reloads the classes in development mode.
}
public void clearPage() {
page.remove(resetAfterRenderBehaviour);
final List<Component> nonInheritedChildren = new ArrayList<Component>();
page.visitChildren(new IVisitor<Component, Object>() {
@Override
public void component(Component child, IVisit<Object> visit) {
if (child.getParent() == page && !inheritedChildren.contains(child)) {
nonInheritedChildren.add(child);
}
}
});
for (Component child : nonInheritedChildren)
page.remove(child);
}
public boolean acceptMissingElements() {
return getConfiguration().getJFDRetentionStrategy() == JfdRetentionStrategy.silentmerge;
}
/**
* Returns true if the GUI represented by this context, is a page rather
* than a (modal) dialog. This is derived from the page attribute's base
* class which differs between pages and (modal) panels
*/
public boolean isPage() {
return getConfiguration().getHTMLPageBaseClass().isAssignableFrom(page.getClass());
}
// private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
// System.out.println("DMDWebGenPageContext deserialized");
// s.defaultReadObject();
// }
}