001: /*
002: * $Id: GeneratorServlet.java,v 1.8 2002/07/15 02:15:03 skavish Exp $
003: *
004: * ===========================================================================
005: *
006: * The JGenerator Software License, Version 1.0
007: *
008: * Copyright (c) 2000 Dmitry Skavish (skavish@usa.net). All rights reserved.
009: *
010: * Redistribution and use in source and binary forms, with or without
011: * modification, are permitted provided that the following conditions are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowlegement:
023: * "This product includes software developed by Dmitry Skavish
024: * (skavish@usa.net, http://www.flashgap.com/)."
025: * Alternately, this acknowlegement may appear in the software itself,
026: * if and wherever such third-party acknowlegements normally appear.
027: *
028: * 4. The name "The JGenerator" must not be used to endorse or promote
029: * products derived from this software without prior written permission.
030: * For written permission, please contact skavish@usa.net.
031: *
032: * 5. Products derived from this software may not be called "The JGenerator"
033: * nor may "The JGenerator" appear in their names without prior written
034: * permission of Dmitry Skavish.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL DMITRY SKAVISH OR THE OTHER
040: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: *
049: */
050:
051: package org.openlaszlo.iv.flash.servlet;
052:
053: import java.rmi.RemoteException;
054: import java.io.*;
055: import java.net.*;
056: import java.util.*;
057:
058: import javax.servlet.*;
059: import javax.servlet.http.*;
060:
061: import org.openlaszlo.iv.flash.api.*;
062: import org.openlaszlo.iv.flash.util.*;
063: import org.openlaszlo.iv.flash.parser.*;
064: import org.openlaszlo.iv.flash.cache.*;
065:
066: import org.openlaszlo.iv.flash.url.*;
067: import org.openlaszlo.iv.flash.api.image.*;
068: import org.openlaszlo.iv.flash.api.sound.*;
069: import org.openlaszlo.iv.flash.context.*;
070:
071: import org.apache.log4j.*;
072: import org.apache.log4j.spi.*;
073:
074: /**
075: * Generator servlet
076: * processes http requests from clients
077: */
078: public class GeneratorServlet extends HttpServlet {
079:
080: private String errorTemplateName = null;
081: private int currentThreadNum = 0;
082: private int maxThreads = 0;
083: private int gcAfterJobsCount = 0;
084: private int jobsCountSinceLastGC = 0;
085: private boolean wrapAssets = false;
086: private boolean createServletContext = false;
087:
088: public void init(ServletConfig config) throws ServletException {
089: super .init(config);
090:
091: // initialize
092: String installDir = getInitParameter("org.openlaszlo.iv.flash.virtualInstallDir");
093: if (installDir != null) {
094: installDir = config.getServletContext().getRealPath(
095: installDir);
096: } else {
097: installDir = getInitParameter("org.openlaszlo.iv.flash.installDir");
098: if (installDir == null) {
099: installDir = config.getServletContext()
100: .getRealPath("/");
101: }
102: }
103:
104: String propFileName = getInitParameter("org.openlaszlo.iv.flash.propertiesFile");
105:
106: Util.init(installDir, propFileName);
107:
108: // initialize stat manager
109: StatManager.init();
110:
111: // set some control variables
112: createServletContext = PropertyManager.getBoolProperty(
113: "org.openlaszlo.iv.flash.servletContext", false);
114: maxThreads = PropertyManager.getIntProperty(
115: "org.openlaszlo.iv.flash.maxThreads", 0);
116: gcAfterJobsCount = PropertyManager.getIntProperty(
117: "org.openlaszlo.iv.flash.garbageCollectAfterJobCount",
118: 0);
119: wrapAssets = PropertyManager.getBoolProperty(
120: "org.openlaszlo.iv.flash.wrapAssets", false);
121:
122: // get name of error template
123: String fileName = PropertyManager.getProperty(
124: "org.openlaszlo.iv.flash.errorTemplate",
125: "bin/error.swt");
126: if (fileName != null) {
127: File errFile = Util.getSysFile(fileName);
128: if (errFile.exists()) {
129: errorTemplateName = errFile.getAbsolutePath();
130: }
131: }
132:
133: Log.info("");
134: Log.logRB(Resource.SERVERSTARTED);
135: Log.info("");
136: }
137:
138: /**
139: * Send error to the client.
140: */
141: private void showTextError(String msg, HttpServletResponse res)
142: throws ServletException, IOException {
143: res.setContentType("text/html");
144: PrintWriter pw = res.getWriter();
145: pw.print("<html><body><font color=red>" + msg
146: + "</font></body></html>");
147: pw.close();
148: }
149:
150: /**
151: * Wrap given exception in our error.swt end send to the client
152: */
153: private void showError(Throwable t, String fileName,
154: HttpServletResponse res) throws ServletException,
155: IOException {
156: String fullMessage = t.getLocalizedMessage();
157:
158: if (errorTemplateName == null) {
159: showTextError(fullMessage, res);
160: } else {
161: try {
162: if (fullMessage == null)
163: fullMessage = t.getClass().getName();
164: StandardContext context = new StandardContext();
165: context.setValue("bulkErrMessage", fullMessage);
166: context.setValue("template_name", fileName);
167: FlashOutput fob = process(errorTemplateName, context,
168: null);
169: send(fob, res);
170: } catch (Throwable tt) {
171: Log.log(tt);
172: showTextError(fullMessage, res);
173: }
174: }
175: }
176:
177: /**
178: * Create generator context from servlet parameters
179: */
180: private Context createServletContext(HttpServletRequest req) {
181: CommandExecutor executor = new OnlineCommandExecutor();
182: CommandContext context = new CommandContext(executor);
183: Enumeration e = req.getParameterNames();
184: while (e.hasMoreElements()) {
185: String name = (String) e.nextElement();
186: String value = Util.processEscapes(req.getParameter(name));
187: context.setValue(name, value);
188: }
189: return context;
190: }
191:
192: protected long getLastModified(HttpServletRequest req) {
193: return -1;
194: }
195:
196: public void doGet(HttpServletRequest req, HttpServletResponse res)
197: throws ServletException, IOException {
198: doGen(req, res);
199: }
200:
201: public void doPost(HttpServletRequest req, HttpServletResponse res)
202: throws ServletException, IOException {
203: doGen(req, res);
204: }
205:
206: public void doGen(HttpServletRequest req, HttpServletResponse res)
207: throws ServletException, IOException {
208: if (maxThreads > 0 && currentThreadNum >= maxThreads) {
209: res.sendError(res.SC_SERVICE_UNAVAILABLE);
210: return;
211: }
212:
213: try {
214: currentThreadNum++;
215:
216: // create context
217: GeneratorServletContext context = null;
218: if (createServletContext) {
219: context = GeneratorServletContext.createContext();
220: context.setHttpServletRequest(req);
221: context.setHttpServletResponse(res);
222: context.setServletConfig(getServletConfig());
223: }
224:
225: String fileName = getRequestedFileName(req);
226:
227: // check for admin requests
228: if (fileName.endsWith("__admin__.swt")) {
229: adminRequest(req, res);
230: return;
231: }
232:
233: processTemplate(fileName, req, res);
234:
235: } finally {
236: currentThreadNum--;
237:
238: // destroy context
239: if (createServletContext)
240: GeneratorServletContext.destroyContext();
241:
242: jobsCountSinceLastGC++;
243: if (gcAfterJobsCount > 0
244: && jobsCountSinceLastGC >= gcAfterJobsCount) {
245: System.runFinalization();
246: System.gc();
247: jobsCountSinceLastGC = 0;
248: }
249: }
250: }
251:
252: /**
253: * Process template
254: */
255: public void processTemplate(String fileName,
256: HttpServletRequest req, HttpServletResponse res)
257: throws ServletException, IOException {
258: long totalTime = System.currentTimeMillis();
259: long processTime = totalTime;
260:
261: FlashOutput fob = null;
262:
263: /*
264: Runtime rt = Runtime.getRuntime();
265: Log.debug("free memory="+rt.freeMemory()+", total memory="+rt.totalMemory()+", free/total="+(rt.freeMemory()*100L/rt.totalMemory())+"%");
266: long cache_size = FontCache.getInstance().getSize();
267: cache_size += MediaCache.getInstance().getSize();
268: cache_size += RequestCache.getInstance().getSize();
269: cache_size += XMLCache.getInstance().getSize();
270: Log.debug("cache size="+cache_size);
271: */
272:
273: Log.logRB(Resource.REQUESTFROM, new Object[] { req
274: .getRemoteHost() });
275:
276: // check for request cache parameters
277: long lifespan = -1L;
278: String key = null;
279: boolean grc = Util.toBool(req.getParameter("grc"), false); // request cache
280: grc = grc || RequestCache.getSettings().isForce();
281: if (grc) {
282: lifespan = Util.toInt(req.getParameter("gre"), -1) * 1000L;
283: // try to retrieve file from cache
284: key = fileName + "?" + req.getQueryString();
285: fob = RequestCache.getRequest(key);
286: }
287:
288: boolean needToCache = grc && fob == null;
289:
290: // process template
291: if (fob == null) {
292: try {
293: Context context = createServletContext(req);
294:
295: String encoding = req.getParameter("genc");
296:
297: if (wrapAssets
298: && (fileName.endsWith(".jpg.swt")
299: || fileName.endsWith(".png.swt") || fileName
300: .endsWith(".gif.swt"))) {
301: fob = wrapImage(fileName.substring(0, fileName
302: .length() - 4), context);
303: } else if (wrapAssets && fileName.endsWith(".mp3.swt")) {
304: fob = wrapSound(fileName.substring(0, fileName
305: .length() - 4), context);
306: } else {
307: fob = process(fileName, context, encoding);
308: }
309:
310: } catch (FileNotFoundException e) {
311: Log.logRB(Resource.FILENOTFOUND,
312: new Object[] { fileName });
313: res.sendError(res.SC_NOT_FOUND);
314: return;
315: } catch (OutOfMemoryError e) {
316: // the only known possible problem with memory may be in a caches, so clean them up
317: FontCache.getInstance().clear();
318: MediaCache.getInstance().clear();
319: RequestCache.getInstance().clear();
320: XMLCache.getInstance().clear();
321: System.gc();
322: Log.logRB(Resource.PROCESSERROR,
323: new Object[] { fileName }, e);
324: showError(e, fileName, res);
325: return;
326: } catch (Throwable e) {
327: Log.logRB(Resource.PROCESSERROR,
328: new Object[] { fileName }, e);
329: showError(e, fileName, res);
330: return;
331: }
332: }
333: processTime = System.currentTimeMillis() - processTime;
334:
335: // cache the result
336: if (needToCache) {
337: RequestCache.addRequest(key, fob, lifespan);
338: }
339:
340: // everything is ok, send movie to the client
341: try {
342: send(fob, res);
343: } catch (Throwable t) {
344: Log.logRB(Resource.SENDERROR, new Object[] { fileName }, t);
345: return;
346: }
347:
348: // log
349: totalTime = System.currentTimeMillis() - totalTime;
350: Log.logRB(Resource.PROCESSREQUEST, new Object[] { fileName,
351: new Integer(fob.getSize()), new Long(processTime),
352: new Long(totalTime) });
353:
354: // generate statistics
355: StatManager.addRequest(fileName, fob.getSize(), processTime,
356: totalTime);
357: }
358:
359: /**
360: * Process template<BR>
361: * <UL>
362: * <LI>parse template
363: * <LI>process (perform substitutions and generator commands)
364: * <LI>generate movie
365: * </UL>
366: *
367: * @param fileName template file name
368: * @param context generator context
369: * @param encoding file encoding
370: * @return generated flash content
371: * @exception IVException
372: * @exception IOException
373: */
374: protected FlashOutput process(String fileName, Context context,
375: String encoding) throws IVException, IOException {
376: FlashFile file = FlashFile.parse(fileName, false, encoding);
377:
378: file.processFile(context);
379:
380: return file.generate();
381: }
382:
383: /**
384: * Wrap image into .swf file
385: * <p>
386: * possible parameters in request:<br>
387: * <CODE>
388: * http://host/image.jpg.swt?width=100&height=200¢er=true
389: * </CODE><br><br>
390: * if there is no width and height then there is no scaling<br>
391: * if there is no center then there is no translating
392: *
393: * @param fileName file name to wrap
394: * @param context generator context
395: * @return wrapped resource
396: * @exception IVException
397: * @exception IOException
398: */
399: protected FlashOutput wrapImage(String fileName, Context context)
400: throws IVException, IOException {
401: IVUrl url = IVUrl.newUrl(fileName);
402:
403: Bitmap bitmap = (Bitmap) MediaCache.getMedia(url);
404: if (bitmap == null) {
405: bitmap = Bitmap.newBitmap(url);
406: }
407: MediaCache.addMedia(url, bitmap, bitmap.getSize(), true);
408:
409: int width = Util.toInt(context.getValue("width"), -1) * 20;
410: int height = Util.toInt(context.getValue("height"), -1) * 20;
411: boolean center = Util.toBool(context.getValue("center"), false);
412: boolean scale = width >= 0 && height >= 0;
413:
414: Instance inst = bitmap
415: .newInstance(width, height, scale, center);
416:
417: Script script = new Script(1);
418: script.setMain();
419:
420: script.newFrame().addInstance(inst, 1);
421:
422: FlashFile file = FlashFile.newFlashFile();
423:
424: file.setFrameSize(inst.getBounds());
425: file.setMainScript(script);
426:
427: return file.generate();
428: }
429:
430: /**
431: * Wrap sound into .swf file
432: * <p>
433: * possible parameters in request:<br>
434: * <code>http://host/image.mp3.swt?delay=100</code>
435: *
436: * @param fileName file name to wrap
437: * @param context generator context
438: * @return wrapped resource
439: * @exception IVException
440: * @exception IOException
441: */
442: protected FlashOutput wrapSound(String fileName, Context context)
443: throws IVException, IOException {
444: Script script = new Script(1);
445: script.setMain();
446:
447: IVUrl url = IVUrl.newUrl(fileName);
448:
449: MP3Sound sound = (MP3Sound) MediaCache.getMedia(url);
450: if (sound == null) {
451: sound = MP3Sound.newMP3Sound(url);
452: }
453: MediaCache.addMedia(url, sound, sound.getSize(), true);
454:
455: // Set the delay if provided
456: int delay = Util.toInt(context.getValue("delay"), 0);
457:
458: if (delay != 0) {
459: sound.setDelaySeek(delay);
460: }
461:
462: SoundInfo soundInfo = SoundInfo.newSoundInfo(0);
463: StartSound startSound = StartSound.newStartSound(sound,
464: soundInfo);
465:
466: script.newFrame().addFlashObject(startSound);
467:
468: FlashFile file = FlashFile.newFlashFile();
469:
470: file.setFrameSize(GeomHelper.newRectangle(0, 0, 0, 0));
471: file.setMainScript(script);
472:
473: return file.generate();
474: }
475:
476: /**
477: * Send generator output buffer to the client
478: *
479: * @param fob flash data to send
480: * @param res response to send to
481: * @exception ServletException
482: * @exception IOException
483: */
484: protected void send(FlashOutput fob, HttpServletResponse res)
485: throws ServletException, IOException {
486: res.setContentLength(fob.getSize());
487: res.setContentType("application/x-shockwave-flash");
488:
489: ServletOutputStream sos = res.getOutputStream();
490: try {
491: sos.write(fob.getBuf(), 0, fob.getSize());
492: } finally {
493: sos.close();
494: }
495: }
496:
497: /**
498: * Returns absolute path of requested template
499: */
500: protected String getRequestedFileName(HttpServletRequest req) {
501: return getServletContext().getRealPath(req.getServletPath());
502: //return req.getRealPath( req.getServletPath() );
503: }
504:
505: private void debugInfo(HttpServletRequest req) {
506: //Log.logRB(Resource.STR,"req.getPathInfo()="+req.getPathInfo());
507: //Log.logRB(Resource.STR,"req.getPathTranslated()="+req.getPathTranslated());
508: //Log.logRB(Resource.STR,"req.getQueryString()="+req.getQueryString());
509: //Log.logRB(Resource.STR,"req.getRealPath()="+req.getRealPath(""));
510: //Log.logRB(Resource.STR,"req.getRequestURI()="+req.getRequestURI());
511: //Log.logRB(Resource.STR,"req.getServletPath()="+req.getServletPath());
512:
513: /* if( server == 0 ) {
514: String wwwRoot = req.getPathTranslated();
515: if( wwwRoot.endsWith( "\\" ) || wwwRoot.endsWith( "/" ) ) wwwRoot = wwwRoot.substring(0,wwwRoot.length()-1);
516: String fileName = wwwRoot+req.getRequestURI();
517: return fileName;
518: } else if( server == 1 ) {
519: }*/
520: }
521:
522: /**
523: * Admin request
524: * <P>
525: * Admin requests come in form of: http://host/__admin__.swt?parm=value&parm=value...
526: * <P>
527: * possible parameters:<BR>
528: * <UL>
529: * <LI>showHTMLStat
530: * </UL>
531: *
532: * @param req
533: * @param res
534: * @exception ServletException
535: * @exception IOException
536: */
537: public void adminRequest(HttpServletRequest req,
538: HttpServletResponse res) throws ServletException,
539: IOException {
540: // very stupid and simple authentication
541: String userName = req.getParameter("username");
542: String password = req.getParameter("password");
543: String myUserName = PropertyManager
544: .getProperty("org.openlaszlo.iv.flash.adminUserName");
545: String myPassword = PropertyManager
546: .getProperty("org.openlaszlo.iv.flash.adminPassword");
547: if (myUserName == null || myPassword == null
548: || userName == null || password == null
549: || userName.length() == 0 || password.length() == 0
550: || !myUserName.equals(userName)
551: || !myPassword.equals(password)) {
552: res.sendError(res.SC_UNAUTHORIZED);
553: return;
554: }
555:
556: // parse admin request
557: if (req.getParameter("showHTMLStat") != null) {
558: showHTMLStat(req, res);
559: } else if (req.getParameter("getCurrentStat") != null) {
560: getCurrentStat(req, res);
561: } else if (req.getParameter("getServerXMLInfo") != null) {
562: getServerXMLInfo(req, res);
563: } else {
564: showTextError("unknown admin request", res);
565: }
566: }
567:
568: /**
569: * Get current stat
570: */
571: public void getCurrentStat(HttpServletRequest req,
572: HttpServletResponse res) throws ServletException,
573: IOException {
574: res.setContentType("application/x-www-urlformencoded");
575: PrintWriter pw = res.getWriter();
576:
577: pw.print("threadsNum=" + currentThreadNum);
578: pw.print("&serverInfo="
579: + URLEncoder
580: .encode(getServletContext().getServerInfo()));
581:
582: StatUnit sinceStartup = StatManager.getSinceStartup();
583: long upTime = System.currentTimeMillis()
584: - sinceStartup.getStartTime();
585: String upTimeStr = (upTime / 3600000) + "h+"
586: + ((upTime / 60000) % 60) + "m+"
587: + ((upTime / 1000) % 60) + "s";
588: pw.print("&upTime=" + upTimeStr);
589: sinceStartup.printVars(pw);
590: pw.close();
591: }
592:
593: /**
594: * Show HTML stat
595: */
596: public void showHTMLStat(HttpServletRequest req,
597: HttpServletResponse res) throws ServletException,
598: IOException {
599: res.setContentType("text/html");
600: PrintWriter pw = res.getWriter();
601: pw.print("<html><body>");
602: pw.print("Current number of threads running: "
603: + currentThreadNum + "<br>");
604: pw.print("<hr>Since startup: ");
605: StatManager.getSinceStartup().print(pw);
606: Vector v = StatManager.getStatistic();
607: for (int i = 0; i < v.size(); i++) {
608: pw.print("<hr>");
609: StatUnit su = (StatUnit) v.elementAt(i);
610: su.print(pw);
611: }
612: pw.print("<hr>");
613: pw.print("</body></html>");
614: pw.close();
615: }
616:
617: /**
618: * Get server info in xml format
619: */
620: public void getServerXMLInfo(HttpServletRequest req,
621: HttpServletResponse res) throws ServletException,
622: IOException {
623: res.setContentType("text/xml");
624: PrintWriter pw = res.getWriter();
625:
626: pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
627: pw.println("<!-- generated by JGenerator servlet -->");
628: //pw.println( "<!DOCTYPE jgenerator-server SYSTEM \"http://www.flashgap.com/jgenerator-server.dtd\">" );
629:
630: pw.println("<jgenerator-server>");
631:
632: StatUnit sinceStartup = StatManager.getSinceStartup();
633:
634: // print server info
635: long upTime = System.currentTimeMillis()
636: - sinceStartup.getStartTime();
637: String upTimeStr = (upTime / 3600000) + "h+"
638: + ((upTime / 60000) % 60) + "m+"
639: + ((upTime / 1000) % 60) + "s";
640: pw.println("<server-info>");
641: pw.println("<threads-num>" + currentThreadNum
642: + "</threads-num>");
643: pw.println("<uptime>" + upTimeStr + "</uptime>");
644: pw.println("<description>"
645: + getServletContext().getServerInfo()
646: + "</description>");
647: pw.println("</server-info>");
648:
649: // print statistics
650: pw.println("<statistics>");
651: sinceStartup.printXML(pw, true);
652: Vector v = StatManager.getStatistic();
653: for (int i = 0; i < v.size(); i++) {
654: StatUnit su = (StatUnit) v.elementAt(i);
655: su.printXML(pw, false);
656: }
657: pw.println("</statistics>");
658:
659: // print properties
660: pw.println("<properties>");
661:
662: /* PropertyManager.getIntProperty( "org.openlaszlo.iv.flash.maxThreads", 0 );
663: PropertyManager.getIntProperty( "org.openlaszlo.iv.flash.garbageCollectAfterJobCount", 0 );
664: PropertyManager.getBoolProperty( "org.openlaszlo.iv.flash.wrapAssets", false );
665: PropertyManager.getProperty("org.openlaszlo.iv.flash.errorTemplate","bin/error.swt");
666: */
667: pw.println("</properties>");
668:
669: pw.println("</jgenerator-server>");
670:
671: pw.close();
672: }
673:
674: /**
675: * Online command executor.
676: * <P>
677: * Adds additional commands for online version of generator
678: */
679: public class OnlineCommandExecutor extends CommandExecutor {
680:
681: }
682:
683: }
|