001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: hannes $
013: * $Revision: 8708 $
014: * $Date: 2007-12-12 13:45:31 +0100 (Mit, 12 Dez 2007) $
015: */
016:
017: package helma.main;
018:
019: import helma.framework.core.*;
020: import helma.framework.repository.Repository;
021: import helma.framework.repository.FileRepository;
022: import helma.util.StringUtils;
023: import org.apache.xmlrpc.XmlRpcHandler;
024: import org.mortbay.http.*;
025: import org.mortbay.http.handler.*;
026: import org.mortbay.jetty.servlet.*;
027: import java.io.*;
028: import java.rmi.*;
029: import java.util.*;
030: import helma.util.ResourceProperties;
031:
032: /**
033: * This class is responsible for starting and stopping Helma applications.
034: */
035: public class ApplicationManager implements XmlRpcHandler {
036: private Hashtable descriptors;
037: private Hashtable applications;
038: private Hashtable xmlrpcHandlers;
039: private int rmiPort;
040: private ResourceProperties props;
041: private Server server;
042: private long lastModified;
043:
044: /**
045: * Creates a new ApplicationManager object.
046: *
047: * @param props the properties defining the running apps
048: * @param server the server instance
049: * @param port The RMI port we're binding to
050: */
051: public ApplicationManager(ResourceProperties props, Server server,
052: int port) {
053: this .props = props;
054: this .server = server;
055: this .rmiPort = port;
056: descriptors = new Hashtable();
057: applications = new Hashtable();
058: xmlrpcHandlers = new Hashtable();
059: lastModified = 0;
060: }
061:
062: /**
063: * Called regularely check applications property file
064: * to create and start new applications.
065: */
066: protected void checkForChanges() {
067: if (props.lastModified() > lastModified) {
068: try {
069: for (Enumeration e = props.keys(); e.hasMoreElements();) {
070: String appName = (String) e.nextElement();
071:
072: if ((appName.indexOf(".") == -1)
073: && (applications.get(appName) == null)) {
074: AppDescriptor appDesc = new AppDescriptor(
075: appName);
076: appDesc.start();
077: appDesc.bind();
078: }
079: }
080:
081: // then stop deleted ones
082: for (Enumeration e = descriptors.elements(); e
083: .hasMoreElements();) {
084: AppDescriptor appDesc = (AppDescriptor) e
085: .nextElement();
086:
087: // check if application has been removed and should be stopped
088: if (!props.containsKey(appDesc.appName)) {
089: appDesc.stop();
090: } else if (server.http != null) {
091: // If application continues to run, remount
092: // as the mounting options may have changed.
093: appDesc.unbind();
094: AppDescriptor ndesc = new AppDescriptor(
095: appDesc.appName);
096: ndesc.app = appDesc.app;
097: ndesc.bind();
098: descriptors.put(ndesc.appName, ndesc);
099: }
100: }
101: } catch (Exception mx) {
102: server.getLogger().error("Error checking applications",
103: mx);
104: }
105:
106: lastModified = System.currentTimeMillis();
107: }
108: }
109:
110: /**
111: * Start an application by name
112: */
113: public void start(String appName) {
114: AppDescriptor desc = new AppDescriptor(appName);
115: desc.start();
116: }
117:
118: /**
119: * Bind an application by name
120: */
121: public void register(String appName) {
122: AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
123: if (desc != null) {
124: desc.bind();
125: }
126: }
127:
128: /**
129: * Stop an application by name
130: */
131: public void stop(String appName) {
132: AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
133: if (desc != null) {
134: desc.stop();
135: }
136: }
137:
138: /**
139: * Start all applications listed in the properties
140: */
141: public void startAll() {
142: try {
143: for (Enumeration e = props.keys(); e.hasMoreElements();) {
144: String appName = (String) e.nextElement();
145:
146: if (appName.indexOf(".") == -1) {
147: String appValue = props.getProperty(appName);
148:
149: if (appValue != null && appValue.length() > 0) {
150: appName = appValue;
151: }
152:
153: AppDescriptor desc = new AppDescriptor(appName);
154: desc.start();
155: }
156: }
157:
158: for (Enumeration e = descriptors.elements(); e
159: .hasMoreElements();) {
160: AppDescriptor appDesc = (AppDescriptor) e.nextElement();
161: appDesc.bind();
162: }
163:
164: lastModified = System.currentTimeMillis();
165: } catch (Exception mx) {
166: server.getLogger().error("Error starting applications", mx);
167: mx.printStackTrace();
168: }
169: }
170:
171: /**
172: * Stop all running applications.
173: */
174: public void stopAll() {
175: for (Enumeration en = descriptors.elements(); en
176: .hasMoreElements();) {
177: try {
178: AppDescriptor appDesc = (AppDescriptor) en
179: .nextElement();
180:
181: appDesc.stop();
182: } catch (Exception x) {
183: // ignore exception in application shutdown
184: }
185: }
186: }
187:
188: /**
189: * Get an array containing all currently running applications.
190: */
191: public Object[] getApplications() {
192: return applications.values().toArray();
193: }
194:
195: /**
196: * Get an application by name.
197: */
198: public Application getApplication(String name) {
199: return (Application) applications.get(name);
200: }
201:
202: /**
203: * Implements org.apache.xmlrpc.XmlRpcHandler.execute()
204: */
205: public Object execute(String method, Vector params)
206: throws Exception {
207: int dot = method.indexOf(".");
208:
209: if (dot == -1) {
210: throw new Exception("Method name \"" + method
211: + "\" does not specify a handler application");
212: }
213:
214: if ((dot == 0) || (dot == (method.length() - 1))) {
215: throw new Exception("\"" + method
216: + "\" is not a valid XML-RPC method name");
217: }
218:
219: String handler = method.substring(0, dot);
220: String method2 = method.substring(dot + 1);
221: Application app = (Application) xmlrpcHandlers.get(handler);
222:
223: if (app == null) {
224: app = (Application) xmlrpcHandlers.get("*");
225: // use the original method name, the handler is resolved within the app.
226: method2 = method;
227: }
228:
229: if (app == null) {
230: throw new Exception("Handler \"" + handler
231: + "\" not found for " + method);
232: }
233:
234: return app.executeXmlRpc(method2, params);
235: }
236:
237: private String getMountpoint(String mountpoint) {
238: mountpoint = mountpoint.trim();
239:
240: if ("".equals(mountpoint)) {
241: return "/";
242: } else if (!mountpoint.startsWith("/")) {
243: return "/" + mountpoint;
244: }
245:
246: return mountpoint;
247: }
248:
249: private String joinMountpoint(String prefix, String suffix) {
250: if (prefix.endsWith("/") || suffix.startsWith("/")) {
251: return prefix + suffix;
252: } else {
253: return prefix + "/" + suffix;
254: }
255: }
256:
257: private String getPathPattern(String mountpoint) {
258: if (!mountpoint.startsWith("/")) {
259: mountpoint = "/" + mountpoint;
260: }
261:
262: if ("/".equals(mountpoint)) {
263: return "/";
264: }
265:
266: if (mountpoint.endsWith("/")) {
267: return mountpoint + "*";
268: }
269:
270: return mountpoint + "/*";
271: }
272:
273: private File getAbsoluteFile(String path) {
274: // make sure our directory has an absolute path,
275: // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
276: File file = new File(path);
277: if (file.isAbsolute()) {
278: return file;
279: } else {
280: return file.getAbsoluteFile();
281: }
282: }
283:
284: /**
285: * Inner class that describes an application and its start settings.
286: */
287: class AppDescriptor {
288:
289: Application app;
290:
291: String appName;
292: File appDir;
293: File dbDir;
294: String mountpoint;
295: String pathPattern;
296: String staticDir;
297: String protectedStaticDir;
298: String staticMountpoint;
299: boolean staticIndex;
300: String[] staticHome;
301: String xmlrpcHandlerName;
302: String cookieDomain;
303: String sessionCookieName;
304: String protectedSessionCookie;
305: String uploadLimit;
306: String uploadSoftfail;
307: String debug;
308: boolean encode;
309: Repository[] repositories;
310:
311: /**
312: * extend apps.properties, add [appname].ignore
313: */
314: String ignoreDirs;
315:
316: /**
317: * Creates an AppDescriptor from the properties.
318: */
319: AppDescriptor(String name) {
320: ResourceProperties conf = props
321: .getSubProperties(name + '.');
322: appName = name;
323: mountpoint = getMountpoint(conf.getProperty("mountpoint",
324: appName));
325: pathPattern = getPathPattern(mountpoint);
326: staticDir = conf.getProperty("static");
327: staticMountpoint = getPathPattern(conf.getProperty(
328: "staticMountpoint", joinMountpoint(mountpoint,
329: "static")));
330: staticIndex = "true".equalsIgnoreCase(conf
331: .getProperty("staticIndex"));
332: String home = conf.getProperty("staticHome");
333: if (home == null) {
334: staticHome = new String[] { "index.html", "index.htm" };
335: } else {
336: staticHome = StringUtils.split(home, ",");
337: }
338: protectedStaticDir = conf.getProperty("protectedStatic");
339:
340: cookieDomain = conf.getProperty("cookieDomain");
341: sessionCookieName = conf.getProperty("sessionCookieName");
342: protectedSessionCookie = conf
343: .getProperty("protectedSessionCookie");
344: uploadLimit = conf.getProperty("uploadLimit");
345: uploadSoftfail = conf.getProperty("uploadSoftfail");
346: debug = conf.getProperty("debug");
347: encode = "true".equalsIgnoreCase(conf
348: .getProperty("responseEncoding"));
349: String appDirName = conf.getProperty("appdir");
350: appDir = (appDirName == null) ? null
351: : getAbsoluteFile(appDirName);
352: String dbDirName = conf.getProperty("dbdir");
353: dbDir = (dbDirName == null) ? null
354: : getAbsoluteFile(dbDirName);
355:
356: // got ignore dirs
357: ignoreDirs = conf.getProperty("ignore");
358:
359: // read and configure app repositories
360: ArrayList repositoryList = new ArrayList();
361: Class[] parameters = { String.class };
362: for (int i = 0; true; i++) {
363: String repositoryArgs = conf.getProperty("repository."
364: + i);
365:
366: if (repositoryArgs != null) {
367: // lookup repository implementation
368: String repositoryImpl = conf
369: .getProperty("repository." + i
370: + ".implementation");
371: if (repositoryImpl == null) {
372: // implementation not set manually, have to guess it
373: if (repositoryArgs.endsWith(".zip")) {
374: repositoryImpl = "helma.framework.repository.ZipRepository";
375: } else if (repositoryArgs.endsWith(".js")) {
376: repositoryImpl = "helma.framework.repository.SingleFileRepository";
377: } else {
378: repositoryImpl = "helma.framework.repository.FileRepository";
379: }
380: }
381:
382: try {
383: Repository newRepository = (Repository) Class
384: .forName(repositoryImpl)
385: .getConstructor(parameters)
386: .newInstance(
387: new Object[] { repositoryArgs });
388: repositoryList.add(newRepository);
389: } catch (Exception ex) {
390: server
391: .getLogger()
392: .error(
393: "Adding repository "
394: + repositoryArgs
395: + " failed. "
396: + "Will not use that repository. Check your initArgs!",
397: ex);
398: }
399: } else {
400: // we always scan repositories 0-9, beyond that only if defined
401: if (i > 9) {
402: break;
403: }
404: }
405: }
406:
407: if (appDir != null) {
408: FileRepository appRep = new FileRepository(appDir);
409: if (!repositoryList.contains(appRep)) {
410: repositoryList.add(appRep);
411: }
412: } else if (repositoryList.isEmpty()) {
413: repositoryList.add(new FileRepository(new File(server
414: .getAppsHome(), appName)));
415: }
416: repositories = new Repository[repositoryList.size()];
417: repositoryList.toArray(repositories);
418: }
419:
420: void start() {
421: server.getLogger().info("Building application " + appName);
422:
423: try {
424: // create the application instance
425: app = new Application(appName, server, repositories,
426: appDir, dbDir);
427:
428: // register ourselves
429: descriptors.put(appName, this );
430: applications.put(appName, app);
431:
432: // the application is started later in the register method, when it's bound
433: app.init(ignoreDirs);
434:
435: // set application URL prefix if it isn't set in app.properties
436: if (!app.hasExplicitBaseURI()) {
437: app.setBaseURI(mountpoint);
438: }
439:
440: app.start();
441: } catch (Exception x) {
442: server.getLogger().error(
443: "Error creating application " + appName, x);
444: x.printStackTrace();
445: }
446: }
447:
448: void stop() {
449: server.getLogger().info("Stopping application " + appName);
450:
451: // unbind application
452: unbind();
453:
454: // stop application
455: try {
456: app.stop();
457: server.getLogger().info(
458: "Stopped application " + appName);
459: } catch (Exception x) {
460: server.getLogger().error("Couldn't stop app", x);
461: }
462:
463: descriptors.remove(appName);
464: applications.remove(appName);
465: }
466:
467: void bind() {
468: try {
469: server.getLogger().info(
470: "Binding application " + appName);
471:
472: // bind to RMI server
473: if (rmiPort > 0) {
474: Naming.rebind("//:" + rmiPort + "/" + appName,
475: new RemoteApplication(app));
476: }
477:
478: // set application URL prefix if it isn't set in app.properties
479: if (!app.hasExplicitBaseURI()) {
480: app.setBaseURI(mountpoint);
481: }
482:
483: // bind to Jetty HTTP server
484: if (server.http != null) {
485:
486: HttpContext context = server.http
487: .addContext(pathPattern);
488:
489: if (encode) {
490: // FIXME: ContentEncodingHandler is broken/removed in Jetty 4.2
491: // context.addHandler(new ContentEncodingHandler());
492: server
493: .getLogger()
494: .warn(
495: "Warning: disabling response encoding for Jetty 4.2 compatibility");
496: }
497:
498: ServletHandler handler = new ServletHandler();
499:
500: ServletHolder holder = handler
501: .addServlet(appName, "/*",
502: "helma.servlet.EmbeddedServletClient");
503:
504: holder.setInitParameter("application", appName);
505: // holder.setInitParameter("mountpoint", mountpoint);
506:
507: if (cookieDomain != null) {
508: holder.setInitParameter("cookieDomain",
509: cookieDomain);
510: }
511:
512: if (sessionCookieName != null) {
513: holder.setInitParameter("sessionCookieName",
514: sessionCookieName);
515: }
516:
517: if (protectedSessionCookie != null) {
518: holder.setInitParameter(
519: "protectedSessionCookie",
520: protectedSessionCookie);
521: }
522:
523: if (uploadLimit != null) {
524: holder.setInitParameter("uploadLimit",
525: uploadLimit);
526: }
527:
528: if (uploadSoftfail != null) {
529: holder.setInitParameter("uploadSoftfail",
530: uploadSoftfail);
531: }
532:
533: if (debug != null) {
534: holder.setInitParameter("debug", debug);
535: }
536:
537: context.addHandler(handler);
538:
539: if (protectedStaticDir != null) {
540: File protectedContent = getAbsoluteFile(protectedStaticDir);
541: context.setResourceBase(protectedContent
542: .getPath());
543: server.getLogger().info(
544: "Serving protected static from "
545: + protectedContent.getPath());
546: }
547:
548: context.start();
549:
550: // if there is a static direcory specified, mount it
551: if (staticDir != null) {
552:
553: File staticContent = getAbsoluteFile(staticDir);
554:
555: server.getLogger().info(
556: "Serving static from "
557: + staticContent.getPath());
558: server.getLogger().info(
559: "Mounting static at "
560: + staticMountpoint);
561:
562: context = server.http
563: .addContext(staticMountpoint);
564: context.setWelcomeFiles(staticHome);
565:
566: context
567: .setResourceBase(staticContent
568: .getPath());
569:
570: ResourceHandler rhandler = new ResourceHandler();
571: rhandler.setDirAllowed(staticIndex);
572: context.addHandler(rhandler);
573: context.start();
574: }
575: }
576:
577: // register as XML-RPC handler
578: xmlrpcHandlerName = app.getXmlRpcHandlerName();
579: xmlrpcHandlers.put(xmlrpcHandlerName, app);
580: // app.start();
581: } catch (Exception x) {
582: server.getLogger().error("Couldn't bind app", x);
583: x.printStackTrace();
584: }
585: }
586:
587: void unbind() {
588: server.getLogger().info("Unbinding application " + appName);
589:
590: try {
591: // unbind from RMI server
592: if (rmiPort > 0) {
593: Naming.unbind("//:" + rmiPort + "/" + appName);
594: }
595:
596: // unbind from Jetty HTTP server
597: if (server.http != null) {
598: HttpContext context = server.http.getContext(null,
599: pathPattern);
600:
601: if (context != null) {
602: context.stop();
603: context.destroy();
604: }
605:
606: if (staticDir != null) {
607: context = server.http.getContext(null,
608: staticMountpoint);
609:
610: if (context != null) {
611: context.stop();
612: context.destroy();
613: }
614: }
615: }
616:
617: // unregister as XML-RPC handler
618: if (xmlrpcHandlerName != null) {
619: xmlrpcHandlers.remove(xmlrpcHandlerName);
620: }
621: } catch (Exception x) {
622: server.getLogger().error("Couldn't unbind app", x);
623: }
624:
625: }
626:
627: public String toString() {
628: return "[AppDescriptor " + app + "]";
629: }
630: }
631: }
|