/*
* Maintainer.java February 2006
*
* Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package simple.page;
import simple.page.translate.Translator;
import simple.page.translate.Reference;
import simple.page.translate.Source;
import simple.page.compile.Compiler;
import simple.http.serve.Context;
import simple.page.Workspace;
import java.util.Hashtable;
import java.util.Set;
import java.io.File;
/**
* The <code>Maintainer</code> object is used to perform background
* compilation of expired JSP sources. This will peroidically check
* all source references to determine whether the JSP sources that
* have been used to create a page class have been modified. If the
* JSP sources have been modified it will recompile and reload the
* page class, with the new JSP sources.
*
* @author Niall Gallagher
*/
final class Maintainer extends Thread {
/**
* This is the translator used to convert JSP sources.
*/
private Translator translator;
/**
* This is the compiler used to compile translated sources.
*/
private Compiler compiler;
/**
* This is the cache used to store the compiled classes.
*/
private Hashtable cache;
/**
* Constructor for the <code>Maintainer</code> object. This will
* construct a background compiler, which is used to check the
* JSP sources for modification. All references are acquired from
* the translator, which may mark several files for monitoring.
*
* @param project this is the project used to locate files
*/
public Maintainer(Workspace project) throws Exception {
this.compiler = new Compiler(project);
this.translator = new Translator(project);
this.cache = new Hashtable();
this.start();
}
/**
* This will acquire the compiled <code>Class</code> representing
* the requested JSP. If the class file has not been found in the
* cache this will translate and compile the JSP before caching it.
*
* @param target this is the URI path to the requested JSP
*/
public synchronized Class retrieve(String target) throws Exception{
Entry entry = (Entry)cache.get(target);
if(entry == null) {
entry = load(target);
}
return entry.getType();
}
/**
* This method is used to load the JSP class without compiling it.
* If the class does not exist then it is compiled. This is used
* for a batch translate and compile step, which allows the JSP
* engine to share an ant compile task across multiple targets.
*
* @param source this is the translated JSP source target
* @param target this is the target that is to be compiled
*/
private synchronized Entry load(String target) throws Exception{
Source source = translator.translate(target);
try {
Class type = compiler.load(source);
Entry entry = new Entry(source, type);
cache.put(target, entry);
}catch(Exception e) {
return produce(target, source);
}
return (Entry)cache.get(target);
}
/**
* This method is used to perform the translation and compilation
* of the referenced JSP. This returns an entry, which contains
* the class file for the JSP as will as a reference to all files
* used to compose the class.
*
* @param target this is the URI path to the requested JSP
*/
private synchronized Entry produce(String target) throws Exception{
return produce(target, translator.translate(target));
}
/**
* This method is used to perform the translation and compilation
* of the referenced JSP. This returns an entry, which contains
* the class file for the JSP as will as a reference to all files
* used to compose the class.
*
* @param target this is the URI path to the requested JSP
* @param source this is the source file for the translated JSP
*/
private synchronized Entry produce(String target, Source source) throws Exception{
try {
Class type = compiler.compile(source);
Entry entry = new Entry(source, type);
cache.put(target, entry);
}finally {
if(!cache.containsKey(target)) {
source.getSource().delete();
}
}
return (Entry)cache.get(target);
}
/**
* This method will iterate over the keys acquired from the cache.
* This makes use of an immutable list of keys to avoid concurrent
* modifications to the cache. Each key checks the cache for an
* entry, if an entry exists the JSP sources are checked.
*/
private synchronized void maintain() {
Set set = cache.keySet();
if(!set.isEmpty()) {
refresh(set.toArray());
}
}
/**
* This method will iterate over the keys acquired from the cache.
* This is handed the cache keys for each maintained JSP source.
* If the key references a modified source it is re-reanslated
* and compiled in the background. All failed files are deleted.
*
* @param list this is the list of target JSP names to produce
*/
private synchronized void refresh(Object[] list) {
for(int i = 0; i < list.length; i++){
Entry entry = (Entry)cache.get(list[i]);
try {
if(entry.isModified()) {
produce(list[i].toString());
}
} catch(Exception e) {
cache.remove(list[i]);
entry.delete();
}
}
}
/**
* This checks all compiled JSP sources every five seconds to
* determine whether the sources have changed. If any sources have
* changed then this will re-translate and re-compile the source.
* If a compilation or translation error occurs with any of the
* maintained sources, that source is deleted to avoid errors.
*/
public void run() {
while(true){
try{
maintain();
Thread.sleep(5000);
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* The <code>Entry</code> object represents a cachable container
* for compiled sources. It keeps track of JSP sources, so that
* they can be checked for modifications, it also keeps the class
* file for the most recent compilation of the JSP target.
*
* @see simple.page.translate.Reference
*/
private class Entry {
/**
* Represents the reference for the collection of JSP files.
*/
private Reference marker;
/**
* Represents the OS file system location of the source file.
*/
private File source;
/**
* Represents the loaded class for for a compiled JSP.
*/
private Class type;
/**
* Constructor for the <code>Entry</code> object. This is used
* to create a cachable container, to keep all required data
* for a compiled JSP file.
*
* @param source this is the source for the translated JSP
* @param type this represents the loaded class for the JSP
*/
public Entry(Source source, Class type) {
this.marker = source.getReference();
this.source = source.getSource();
this.type = type;
}
/**
* Checks to see if the sources for the JSP have been recently
* been modified. If the sources have been modified this will
* return true, this also returns true if the JSP is deleted.
*
* @return true if the JSP source files have been modified
*/
public boolean isModified() {
return marker.isModified();
}
/**
* Returns the compiled and loaded class for the JSP sources.
* This ensures that a page can be served, even if the JSP
* source files are being re-compiled in the background.
*
* @return this returns the compiled and loaded page class
*/
public Class getType() {
return type;
}
/**
* This method is used to delete the source file for this entry.
* This is used when the compilation of a source file fails, it
* ensures that the Ant compiler does not constantly fail.
*/
public void delete() {
source.delete();
}
}
}
|