package dk.brics.jwig;
import dk.brics.jwig.server.ThreadContext;
import dk.brics.jwig.util.RandomString;
import dk.brics.xact.ToXMLable;
import dk.brics.xact.XML;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Handler that produces XML data when an object it depends on has been changed.
*/
@Regenerable
public class XMLProducer extends AbstractHandler implements ToXMLable {
private final String pageurl;
private final Object[] objs;
/**
* Constructs a new XML producer.
* When notified by one of the given objects, the <code>run</code>
* method is invoked for recomputing the XML data, and the active clients are informed.
* The <code>run</code> method is assumed to be 'safe' (in the HTTP sense).
*
* @see WebContext#update(Object)
* @see EventHandler#getProducer()
*/
public XMLProducer(Object... dependencies) {
super(dependencies);
this.objs = dependencies;
pageurl = ThreadContext.get().getRequestURL();
}
/**
* Invoked when a new result has been computed.
* Invalidates the cached value and informs the active clients.
*/
public void update() { // FIXME: invoke from DependencyMap on notifications
ThreadContext.getCache().remove(pageurl);
ThreadContext.getSynchronizer().update(getHandlerIdentifier());
}
/**
* Returns an XML value obtained by invoking <code>run</code>, together with
* a JavaScript instruction for automatic updating.
*/
@Override
public XML toXML() {
XML xml = invokeRun();
Response p = makeResponse(xml); //Make a cached response that stores the value of the XML generated by the producer (and tag it with an ETAG)
String handlerCacheIdentifier = getCacheAugmentedString(getHandlerIdentifier());
ThreadContext.getCache().put(handlerCacheIdentifier, p); //Manually store the value in the cache for later requests
String id = "s" + RandomString.get(12);
return XML.parseTemplate(
"<script type=\"text/javascript\" id=[ID]>jwig.startXML('<[URL]>','\"<[ETAG]>\"','<[ID]>')</script>" +
"<[VALUE]>" +
"<script type=\"text/javascript\">jwig.endXML()</script>")
.plug("URL", handlerCacheIdentifier)
.plug("VALUE", xml)
.plug("ETAG", p.getETag()) //This ETAG is used to the check the cache for the XML generated by the producer
.plug("ID", id);
}
/**
* Invokes the <code>run</code> and returns the XML result.
*/
XML invokeRun() {
try {
for (Object obs : objs) {
if (obs != null) {
ThreadContext.getDependencyMap().addDependency(this, obs);
}
}
ThreadContext c = ThreadContext.get();
Method m = getClass().getDeclaredMethod("run");
XMLProducer previous = c.getProducer();
c.setProducer(this);
m.setAccessible(true);
XML xml = (XML) m.invoke(this);
c.setProducer(previous);
return xml;
} catch (NoSuchMethodException e) {
throw new JWIGException(e);
} catch (IllegalArgumentException e) {
throw new JWIGException(e);
} catch (IllegalAccessException e) {
throw new JWIGException(e);
} catch (InvocationTargetException e) {
throw new JWIGException(e);
}
}
/**
* Constructs a cached response.
* The response is also wrapped into a dummy root element.
*/
private Response makeResponse(XML xml) {
xml = wrap(xml);
Response p = new Response();
p.setXML(xml);
p.setContentType("text/plain");
return p;
}
private XML wrap(XML xml) {
if (xml != null)
xml = XML.parseTemplate("<div/>").setContent(xml);
return xml;
}
/**
* Invokes the <code>run</code> method of the handler, caches the result, and sends it to the client.
*/
@Override
Object process(String referer) {
XML xml = wrap(invokeRun());
getResponse().setContentType("text/plain");
return xml;
}
/**
* Invoked when the current response is invalidated.
*/
@Override
public void destroy() {
ThreadContext.getCache().remove(getHandlerIdentifier());
}
}