/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.atomojo.www.util.script;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import org.atomojo.app.client.Feed;
import org.atomojo.app.client.FeedClient;
import org.atomojo.app.client.FeedDestination;
import org.atomojo.app.client.Link;
import org.atomojo.app.client.Term;
import org.infoset.xml.Document;
import org.infoset.xml.XMLException;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.routing.Template;
/**
*
* @author alex
*/
public class ScriptManager {
public static String ATTR = "org.atomojo.www.util.script.manager";
public class ScriptContext {
URI location;
Transformer xform;
MediaType type;
ScriptContext(URI location,Transformer xform,MediaType type) {
this.location = location;
this.xform = xform;
this.type = type;
}
public MediaType getMediaType() {
return type;
}
public Transformer getTransformer() {
return xform;
}
public void release() {
cache.release(location, xform);
}
}
public class Entry {
Date edited;
String id;
Set<Term> criteria;
String match;
MediaType mediaType;
URI location;
int priority;
Entry(String id,Date edited,Set<Term> criteria,String match,int priority,MediaType mediaType,URI location) {
this.id = id;
this.edited = edited;
this.criteria = criteria;
this.match = match;
this.mediaType = mediaType;
this.location = location;
this.priority = priority;
}
public String getId() {
return id;
}
public Date getEdited() {
return edited;
}
}
class TermSet {
Set<Term> terms;
long updated;
TermSet() {
terms = new TreeSet<Term>();
updated = System.currentTimeMillis();
}
}
class Result {
Entry script;
long updated;
Result(Entry script) {
this.script = script;
updated = System.currentTimeMillis();
}
}
Logger log;
Client client;
long expiration;
Map<String,TermSet> termCache;
Map<String,Result> resultCache;
Map<String,Entry> scripts;
Link metadata;
String username;
String password;
ScriptCache cache;
public ScriptManager(Context context,Client client,Link metadata,String username,String password,long expiration) {
this.log = context.getLogger();
this.client = client;
this.scripts = new TreeMap<String,Entry>();
this.termCache = new TreeMap<String,TermSet>();
this.resultCache = new TreeMap<String,Result>();
this.metadata = metadata;
this.username = username;
this.password = password;
this.cache = new ScriptCache();
this.expiration = expiration;
}
public ScriptContext findScript(Request request,String path)
throws IOException,TransformerException
{
Result result = resultCache.get(path);
if (result!=null && (System.currentTimeMillis()-result.updated)<expiration) {
return createContext(request,result.script);
}
TermSet termSet = termCache.get(path);
if (termSet==null || (System.currentTimeMillis()-termSet.updated)>=expiration) {
termSet = updateTermSet(path,termSet);
}
boolean isFineLog = log.isLoggable(Level.FINE);
if (isFineLog) {
for (Term t : termSet.terms) {
log.fine("Feed term: "+t.getURI());
}
}
List<Entry> matches = new ArrayList<Entry>();
for (Entry entry : scripts.values()) {
if (entry.match==null && entry.criteria.size()==0) {
matches.add(entry);
continue;
}
if (entry.match!=null) {
if (isFineLog) {
log.fine("Checking "+entry.match+" against path "+path);
}
Template template = new Template(entry.match);
if (template.match(path)<0) {
continue;
}
if (isFineLog) {
log.fine("Matched layout entry "+entry.getId()+" using URI match "+entry.match);
}
if (entry.criteria.size()==0) {
matches.add(entry);
}
}
if (entry.criteria.size()>0) {
if (isFineLog) {
for (Term t : entry.criteria) {
log.fine("layout term: "+t.getURI());
}
}
boolean ok = true;
for (Term term : entry.criteria) {
boolean found = false;
for (Term other : termSet.terms) {
if (other.getURI().equals(term.getURI())) {
found = true;
break;
}
}
if (!found) {
ok = false;
break;
}
}
if (ok) {
if (isFineLog) {
log.fine("Matched layout entry "+entry.getId()+", href="+entry.location);
}
matches.add(entry);
}
}
}
Entry matched = null;
for (Entry entry : matches) {
if (matched==null) {
matched = entry;
} else if (entry.priority>matched.priority) {
matched = entry;
}
}
if (matched!=null) {
if (isFineLog) {
log.fine("Using layout entry "+matched.getId()+", href="+matched.location);
}
if (result!=null) {
result.updated = System.currentTimeMillis();
result.script = matched;
} else {
result = new Result(matched);
resultCache.put(path,result);
}
}
return matched==null ? null : createContext(request,matched);
}
protected ScriptContext createContext(Request request,Entry script)
throws IOException,TransformerException
{
Transformer xform = cache.get(script.location);
// TODO: hack for browser detection.
// TODO: browser detection should be part of the rule selection
boolean isIE = request.getClientInfo().getAgent().indexOf("MSIE")>=0;
return new ScriptContext(script.location,xform,isIE && script.mediaType==MediaType.APPLICATION_XHTML_XML ? MediaType.TEXT_HTML : script.mediaType);
}
protected TermSet updateTermSet(String path,TermSet termSet)
{
if (termSet==null) {
termSet = new TermSet();
termCache.put(path,termSet);
}
Reference ref = new Reference(metadata.getLink().toString()+path);
log.info("Updating metadata for "+path+" from "+ref);
FeedClient metadataClient = new FeedClient(client,ref);
if (metadata.getUsername()!=null) {
metadataClient.setIdentity(metadata.getUsername(),metadata.getPassword());
}
final Set<Term> updateSet = termSet.terms;
try {
Response response = metadataClient.get(new FeedDestination() {
public void onFeed(Document feedDoc)
throws XMLException
{
Feed feed = new Feed(feedDoc);
feed.index();
updateSet.clear();
updateSet.addAll(feed.getTerms().values());
if (log.isLoggable(Level.FINE)) {
for (Term t : feed.getTerms().values()) {
log.fine("term: "+t.getURI());
}
}
}
public void onEntry(Document entryDoc) {
}
});
if (!response.getStatus().isSuccess() && response.getStatus().getCode()!=404) {
log.severe("Cannot get metadata from "+ref+", status="+response.getStatus().getCode());
termSet.updated = 0;
}
} catch (Exception ex) {
log.log(Level.SEVERE,"Cannot get feed metadata.",ex);
}
return termSet;
}
public Entry get(String id)
{
return scripts.get(id);
}
public void reload(String id,Date edited,Set<Term> criteria,String match,int priority,MediaType mediaType)
{
Entry entry = get(id);
if (entry==null) {
return;
}
log.fine("Reloaded script entry:\nid="+id+"\npriority="+priority+"\nmatch="+match+"\nmediaType="+mediaType+"\nlocation="+entry.location);
for (Term t : criteria) {
log.fine("criteria: "+t);
}
entry.criteria = criteria;
entry.edited = edited;
entry.match = match;
entry.mediaType = mediaType;
entry.priority = priority;
cache.reload(entry.location);
}
public void add(String id,Date edited,Set<Term> criteria,String match,int priority,MediaType mediaType,URI location)
{
Entry entry = new Entry(id,edited,criteria,match,priority,mediaType,location);
log.fine("Adding script entry:\nid="+id+"\npriority="+priority+"\nmatch="+match+"\nmediaType="+mediaType+"\nlocation="+entry.location);
for (Term t : criteria) {
log.fine("criteria: "+t);
}
cache.add(location, username, password, true);
scripts.put(id,entry);
}
public Set<String> getKeys() {
return scripts.keySet();
}
public void remove(String id) {
Entry entry = scripts.remove(id);
if (entry!=null) {
List<String> toRemove = new ArrayList<String>();
for (String path : resultCache.keySet()) {
Result result = resultCache.get(path);
if (result.script==entry) {
toRemove.add(path);
}
}
for (String path : toRemove) {
resultCache.remove(path);
}
cache.remove(entry.location);
}
}
}