/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.web.dcui;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import divconq.cms.feed.FeedAdapter;
import divconq.lang.op.OperationCallback;
import divconq.lang.op.OperationContext;
import divconq.web.IOutputAdapter;
import divconq.web.WebContext;
import divconq.web.ui.adapter.DcuiOutputAdapter;
import divconq.xml.XElement;
public class Fragment extends Element {
protected AtomicInteger hasfuture = null;
protected List<OperationCallback> callbacks = null;
protected XElement source = null;
//protected Path path = null;
//protected CommonPath loc = null;
protected GroovyObject script = null;
protected FeedAdapter feed = null;
public XElement getSource() {
return this.source;
}
public FeedAdapter getFeed() {
return this.feed;
}
public GroovyObject getServerScript() {
return this.script;
}
public void initializePart(WebContext ctx, IOutputAdapter adapter, OperationCallback cb, Object... args) {
this.coreInitialize(ctx, adapter, new OperationCallback() {
@Override
public void callback() {
Fragment.this.myArguments = new Object[] { args, ctx.getDomain().parseXml(ctx, Fragment.this.source.find("Skeleton")) };
cb.complete();
}
});
}
public void initializeRoot(WebContext ctx, IOutputAdapter adapter, OperationCallback cb) {
this.coreInitialize(ctx, adapter, new OperationCallback() {
@Override
public void callback() {
Fragment.this.myArguments = new Object[] { ctx.getDomain().parseElement(ctx, Fragment.this.source) };
cb.complete();
}
});
}
public void coreInitialize(WebContext ctx, IOutputAdapter adapter, OperationCallback cb) {
//this.path = adapter.getFile().getFilePath(); // TODO cleanup, we want to avoid dependency on Path in this case
//this.loc = adapter.getPath();
this.source = (XElement) ((DcuiOutputAdapter)adapter).getSource().deepCopy();
// if this is a CMS page then build it if Auto
if ("auto".equals(this.source.getAttribute("Cms", "false").toLowerCase())) {
// possible to override the file path and grab a random Page from `feed`
String cmspath = this.source.getAttribute("CmsPath", ctx.getRequest().getPath().toString());
this.feed = ctx.getFeedAdapter("Pages", cmspath);
if (this.feed != null)
this.feed.buildHtmlPage(ctx, this.source);
}
XElement screl = this.source.find("ServerScript");
if (screl != null) {
try (GroovyClassLoader loader = new GroovyClassLoader()) {
Path dpath = OperationContext.get().getDomain().resolvePath("/glib");
if (Files.exists(dpath))
loader.addClasspath(dpath.toString());
Class<?> groovyClass = loader.parseClass(screl.getText());
this.script = (GroovyObject) groovyClass.newInstance();
this.tryExecuteMethod("run", new Object[] { ctx, this, cb });
}
catch (Exception x) {
OperationContext.get().error("Unable to prepare web page server script: " + x);
}
}
else {
cb.callback();
}
}
public void tryExecuteMethod(String name, Object... params) {
if (this.script == null)
return;
Method runmeth = null;
for (Method m : this.script.getClass().getMethods()) {
if (!m.getName().equals(name))
continue;
runmeth = m;
break;
}
if (runmeth == null)
return;
try {
this.script.invokeMethod(name, params);
}
catch (Exception x) {
OperationContext.get().error("Unable to execute watcher script!");
OperationContext.get().error("Error: " + x);
}
}
@Override
public void doBuild(WebContext ctx) {
// if I am not root, copy my functions to root
if (this.getViewRoot() != this) {
for (XElement func : this.source.selectAll("Function"))
this.getViewRoot().getSource().add(func);
}
super.doBuild(ctx);
}
public void write(WebContext ctx) throws IOException {
this.stream(ctx, ctx.getResponse().getPrintStream(), "", false, true);
}
@Override
public void stream(WebContext ctx, PrintStream strm, String indent, boolean firstchild, boolean fromblock) {
if (this.children.size() == 0)
return;
boolean fromon = fromblock;
boolean lastblock = false;
boolean firstch = this.getBlockIndent(); // only true once, and only if
// bi
for (Node node : this.children) {
if (node.getBlockIndent() && !lastblock && !fromon)
this.print(ctx, strm, "", true, "");
node.stream(ctx, strm, indent, (firstch || lastblock), this.getBlockIndent());
lastblock = node.getBlockIndent();
firstch = false;
fromon = false;
}
}
@Override
public boolean writeDynamic(PrintStream buffer, String tabs, boolean first) {
if (this.children.size() == 0)
return false;
for (Node child : this.children) {
if (child.writeDynamic(buffer, tabs, first))
first = false;
}
return true;
}
public void incrementFuture() {
synchronized (this) {
if (this.hasfuture == null)
this.hasfuture = new AtomicInteger();
this.hasfuture.incrementAndGet();
}
}
public void decrementFuture() {
synchronized (this) {
int cnt = (this.hasfuture != null) ? this.hasfuture.decrementAndGet() : 0;
if (cnt == 0) {
if (this.callbacks != null) {
for (OperationCallback cb : this.callbacks)
cb.complete();
}
}
}
}
public void awaitForFutures(OperationCallback cb) {
// at this point it is too late to register any new futures (see
// increment above)
// it is safe to callback immediately if there was no futures
if (this.hasfuture == null) {
cb.complete();
return;
}
synchronized (this) {
if (this.hasfuture.get() == 0) {
cb.complete();
return;
}
if (this.callbacks == null)
this.callbacks = new ArrayList<OperationCallback>();
this.callbacks.add(cb);
}
}
}