001: // Copyright (c) 2003 Per M.A. Bothner.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.kawa.servlet;
005:
006: import gnu.expr.*;
007: import gnu.mapping.*;
008: import gnu.text.*;
009: import java.io.*;
010: import java.net.*;
011: import java.util.*;
012: import javax.servlet.*;
013: import javax.servlet.http.*;
014:
015: /**
016: * The Kawa servlet interpreter
017: *
018: * This servlet is responsible for reading and interpeting Kawa language files
019: * using the QEXO GNU library.
020: *
021: * The implementation borrows ideas from Apache Jakarta Tomcat Jasper.
022: *
023: * @author Ivelin Ivanov
024: * @author Tom Reilly
025: * @author Per Bothner
026: */
027: public class KawaPageServlet extends KawaServlet {
028: private ServletContext context;
029:
030: public void init(ServletConfig config) throws ServletException {
031: super .init(config);
032: context = config.getServletContext();
033: }
034:
035: public void run(CallContext ccontext) throws Throwable {
036: ServletCallContext ctx = (ServletCallContext) ccontext;
037: HttpServletRequest request = ctx.request;
038: HttpServletResponse response = ctx.response;
039:
040: boolean saveClass = request.getParameter("qexo-save-class") != null;
041: String path = request.getServletPath();
042: Object mod = getModule(ctx, path, saveClass, response);
043:
044: if (mod instanceof ModuleBody)
045: ((ModuleBody) mod).run(ctx);
046: }
047:
048: private static final String MODULE_MAP_ATTRIBUTE = "gnu.kawa.module-map";
049:
050: private Object getModule(ServletCallContext ctx, String path,
051: boolean saveClass, HttpServletResponse response)
052: throws Exception {
053: Hashtable mmap = (Hashtable) context
054: .getAttribute(MODULE_MAP_ATTRIBUTE);
055: if (mmap == null) {
056: mmap = new Hashtable();
057: context.setAttribute(MODULE_MAP_ATTRIBUTE, mmap);
058: }
059: ModuleContext mcontext = (ModuleContext) context
060: .getAttribute("gnu.kawa.module-context");
061: if (mcontext == null)
062: mcontext = ModuleContext.getContext();
063: ModuleInfo minfo = (ModuleInfo) mmap.get(path);
064: long now = System.currentTimeMillis();
065: ModuleManager mmanager = mcontext.getManager();
066:
067: // avoid hitting the disk too much
068: if (minfo != null
069: && now - minfo.lastCheckedTime < mmanager.lastModifiedCacheTime)
070: return mcontext.findInstance(minfo);
071:
072: int plen = path.length();
073: // If the path matches a directory rather than a file, keep looking.
074: URL url = (plen == 0 || path.charAt(plen - 1) == '/') ? null
075: : context.getResource(path);
076: String upath = path;
077: if (url == null) {
078: String xpath = path;
079: for (;;) {
080: int sl = xpath.lastIndexOf('/');
081: if (sl < 0) {
082: ctx.response.reset();
083: ctx.response.sendError(
084: HttpServletResponse.SC_NOT_FOUND, path);
085: return null;
086: }
087: xpath = xpath.substring(0, sl);
088: upath = xpath + "/+default+";
089: url = context.getResource(upath);
090: if (url != null)
091: break;
092: }
093: }
094:
095: if (url == null) {
096: ctx.response.reset();
097: ctx.response.sendError(HttpServletResponse.SC_NOT_FOUND,
098: path);
099: return null;
100: }
101:
102: URLConnection connection = url.openConnection();
103: String urlString = url.toExternalForm();
104: long lastModified = connection.getLastModified();
105: if (minfo == null
106: || !urlString.equals(minfo.getSourceAbsPathname()))
107: minfo = mmanager.findWithURL(url);
108: if (minfo.lastModifiedTime == lastModified) {
109: minfo.lastCheckedTime = now;
110: return mcontext.findInstance(minfo);
111: }
112:
113: minfo.lastModifiedTime = lastModified;
114: minfo.lastCheckedTime = now;
115: mmap.put(path, minfo);
116:
117: InputStream resourceStream = connection.getInputStream();
118:
119: Language language = Language
120: .getInstanceFromFilenameExtension(path);
121: if (language == null)
122: language = Language.detect(resourceStream);
123: if (language == null) {
124: if (path != upath) {
125: ctx.response.reset();
126: ctx.response.sendError(
127: HttpServletResponse.SC_NOT_FOUND, path);
128: return null;
129: }
130: String contentType = context.getMimeType(path);
131: response.setContentType(contentType);
132: ServletOutputStream out = response.getOutputStream();
133: byte[] buffer = new byte[4 * 1024];
134: for (;;) {
135: int n = resourceStream.read(buffer);
136: if (n < 0)
137: break;
138: out.write(buffer, 0, n);
139: }
140: resourceStream.close();
141: return null;
142: }
143:
144: InPort port = new InPort(resourceStream, minfo
145: .getSourceAbsPath());
146: Language.setDefaultLanguage(language);
147: SourceMessages messages = new SourceMessages();
148: Compilation comp;
149: try {
150: comp = language.parse(port, messages,
151: Language.PARSE_IMMEDIATE);
152: /*
153: int dot = path.indexOf('.');
154: if (dot < 0)
155: dot = path.length();
156: String name = path.substring(path.lastIndexOf('/')+1, dot);
157: comp.getModule().setName(name);
158: */
159: language.resolve(comp);
160: } catch (SyntaxException ex) {
161: if (ex.getMessages() != messages)
162: throw ex;
163: // Otherwise handled below ...
164: comp = null; // Needed to avoid spurious compilation error.
165: }
166:
167: Class cl = null;
168: if (!messages.seenErrors()) {
169: ModuleExp mexp = comp.getModule();
170: comp.addMainClass(mexp);
171: comp.walkModule(mexp);
172: comp.setState(Compilation.WALKED);
173: cl = ModuleExp.evalToClass(comp, url);
174: }
175:
176: // FIXME: we could output a nice pretty HTML table of the errors
177: // or show the script with the errors highlighted, for bonus
178: // points the pretty page could be generated by a precompiled
179: // xql script with the errors passed as XML somehow and accessed
180: // via input()
181: if (messages.seenErrors()) {
182: ctx.response.reset();
183: ServletOutputStream out = ctx.response.getOutputStream();
184: out.print(messages.toString(20));
185: return null;
186: }
187:
188: minfo.moduleClass = cl;
189: minfo.className = cl.getName();
190:
191: if (saveClass)
192: comp
193: .outputClass(context.getRealPath("WEB-INF/classes") + '/');
194:
195: return mcontext.findInstance(minfo);
196: }
197: }
|