0001: /*
0002: * Helma License Notice
0003: *
0004: * The contents of this file are subject to the Helma License
0005: * Version 2.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://adele.helma.org/download/helma/license.txt
0008: *
0009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
0010: *
0011: * $RCSfile$
0012: * $Author: hannes $
0013: * $Revision: 8659 $
0014: * $Date: 2007-11-26 15:46:36 +0100 (Mon, 26 Nov 2007) $
0015: */
0016:
0017: package helma.framework.core;
0018:
0019: import helma.extensions.ConfigurationException;
0020: import helma.extensions.HelmaExtension;
0021: import helma.framework.*;
0022: import helma.framework.repository.*;
0023: import helma.main.Server;
0024: import helma.objectmodel.*;
0025: import helma.objectmodel.db.*;
0026: import helma.util.*;
0027: import helma.doc.DocApplication;
0028:
0029: import java.io.*;
0030: import java.lang.reflect.*;
0031: import java.rmi.*;
0032: import java.util.*;
0033: import org.apache.commons.logging.Log;
0034: import org.apache.commons.logging.LogFactory;
0035: import java.util.ArrayList;
0036:
0037: /**
0038: * The central class of a Helma application. This class keeps a pool of
0039: * request evaluators (threads with JavaScript interpreters), waits for
0040: * requests from the Web server or XML-RPC port and dispatches them to
0041: * the evaluators.
0042: */
0043: public final class Application implements Runnable {
0044: // the name of this application
0045: private String name;
0046:
0047: // application sources
0048: ArrayList repositories;
0049:
0050: // properties and db-properties
0051: ResourceProperties props;
0052:
0053: // properties and db-properties
0054: ResourceProperties dbProps;
0055:
0056: // This application's main directory
0057: File appDir;
0058:
0059: // Helma server hopHome directory
0060: File hopHome;
0061:
0062: // embedded db directory
0063: File dbDir;
0064:
0065: // this application's node manager
0066: protected NodeManager nmgr;
0067:
0068: // the root of the website, if a custom root object is defined.
0069: // otherwise this is managed by the NodeManager and not cached here.
0070: Object rootObject = null;
0071: String rootObjectClass;
0072:
0073: // The session manager
0074: SessionManager sessionMgr;
0075:
0076: /**
0077: * The type manager checks if anything in the application's prototype definitions
0078: * has been updated prior to each evaluation.
0079: */
0080: public TypeManager typemgr;
0081:
0082: /**
0083: * The skin manager for this application
0084: */
0085: protected SkinManager skinmgr;
0086:
0087: /**
0088: * Collections for evaluator thread pooling
0089: */
0090: protected Stack freeThreads;
0091: protected Vector allThreads;
0092: boolean running = false;
0093: boolean debug;
0094: long starttime;
0095: Hashtable dbSources;
0096:
0097: // map of app modules reflected at app.modules
0098: Map modules;
0099:
0100: // internal worker thread for scheduler, session cleanup etc.
0101: Thread worker;
0102: // request timeout defaults to 60 seconds
0103: long requestTimeout = 60000;
0104: ThreadGroup threadgroup;
0105:
0106: // threadlocal variable for the current RequestEvaluator
0107: ThreadLocal currentEvaluator = new ThreadLocal();
0108:
0109: // Map of requesttrans -> active requestevaluators
0110: Hashtable activeRequests;
0111:
0112: String logDir;
0113:
0114: // Two logs for each application
0115: Log eventLog;
0116: Log accessLog;
0117:
0118: // Symbolic names for each log
0119: String eventLogName;
0120: String accessLogName;
0121:
0122: // A transient node that is shared among all evaluators
0123: protected INode cachenode;
0124:
0125: // some fields for statistics
0126: protected volatile long requestCount = 0;
0127: protected volatile long xmlrpcCount = 0;
0128: protected volatile long errorCount = 0;
0129:
0130: // the URL-prefix to use for links into this application
0131: private String baseURI;
0132: // the name of the root prototype as far as href() is concerned
0133: private String hrefRootPrototype;
0134:
0135: // the id of the object to use as root object
0136: String rootId = "0";
0137:
0138: // Db mappings for some standard prototypes
0139: private DbMapping rootMapping;
0140: private DbMapping userRootMapping;
0141: private DbMapping userMapping;
0142:
0143: // name of response encoding
0144: String charset;
0145:
0146: // password file to use for authenticate() function
0147: private CryptResource pwfile;
0148:
0149: // Map of java class names to object prototypes
0150: ResourceProperties classMapping;
0151:
0152: // Map of extensions allowed for public skins
0153: Properties skinExtensions;
0154:
0155: // time we last read the properties file
0156: private long lastPropertyRead = -1L;
0157:
0158: // the set of prototype/function pairs which are allowed to be called via XML-RPC
0159: private HashSet xmlrpcAccess;
0160:
0161: // the name under which this app serves XML-RPC requests. Defaults to the app name
0162: private String xmlrpcHandlerName;
0163:
0164: // the list of currently active cron jobs
0165: Hashtable activeCronJobs = null;
0166: // the list of custom cron jobs
0167: Hashtable customCronJobs = null;
0168:
0169: private ResourceComparator resourceComparator;
0170: private Resource currentCodeResource;
0171:
0172: // Field to cache unmapped java classes
0173: private final static String CLASS_NOT_MAPPED = "(unmapped)";
0174:
0175: /**
0176: * Namespace search path for global macros
0177: */
0178: String[] globalMacroPath = null;
0179:
0180: /**
0181: * Simple constructor for dead application instances.
0182: */
0183: public Application(String name) {
0184: this .name = name;
0185: }
0186:
0187: /**
0188: * Build an application with the given name with the given sources. No
0189: * Server-wide properties are created or used.
0190: */
0191: public Application(String name, Repository[] repositories,
0192: File dbDir) throws RemoteException,
0193: IllegalArgumentException {
0194: this (name, null, repositories, null, dbDir);
0195: }
0196:
0197: /**
0198: * Build an application with the given name and server instance. The
0199: * app directories will be created if they don't exist already.
0200: */
0201: public Application(String name, Server server)
0202: throws RemoteException, IllegalArgumentException {
0203: this (name, server, new Repository[0], null, null);
0204: }
0205:
0206: /**
0207: * Build an application with the given name, server instance, sources and
0208: * db directory.
0209: */
0210: public Application(String name, Server server,
0211: Repository[] repositories, File customAppDir,
0212: File customDbDir) throws RemoteException,
0213: IllegalArgumentException {
0214: if ((name == null) || (name.trim().length() == 0)) {
0215: throw new IllegalArgumentException(
0216: "Invalid application name: " + name);
0217: }
0218:
0219: if (repositories.length == 0) {
0220: throw new java.lang.IllegalArgumentException(
0221: "No sources defined for application: " + name);
0222: }
0223:
0224: this .name = name;
0225:
0226: this .repositories = new ArrayList();
0227: this .repositories.addAll(Arrays.asList(repositories));
0228: resourceComparator = new ResourceComparator(this );
0229:
0230: appDir = customAppDir;
0231: dbDir = customDbDir;
0232:
0233: // system-wide properties, default to null
0234: ResourceProperties sysProps;
0235:
0236: // system-wide properties, default to null
0237: ResourceProperties sysDbProps;
0238:
0239: sysProps = sysDbProps = null;
0240: hopHome = null;
0241:
0242: if (server != null) {
0243: hopHome = server.getHopHome();
0244:
0245: if (dbDir == null) {
0246: dbDir = new File(server.getDbHome(), name);
0247: }
0248:
0249: // get system-wide properties
0250: sysProps = server.getProperties();
0251: sysDbProps = server.getDbProperties();
0252: }
0253:
0254: if (!dbDir.exists()) {
0255: dbDir.mkdirs();
0256: }
0257:
0258: if (appDir == null) {
0259: for (int i = repositories.length - 1; i >= 0; i--) {
0260: if (repositories[i] instanceof FileRepository) {
0261: appDir = new File(repositories[i].getName());
0262: break;
0263: }
0264: }
0265: }
0266:
0267: // give the Helma Thread group a name so the threads can be recognized
0268: threadgroup = new ThreadGroup("TX-" + name);
0269:
0270: // create app-level properties
0271: props = new ResourceProperties(this , "app.properties", sysProps);
0272:
0273: // get log names
0274: accessLogName = props.getProperty("accessLog",
0275: new StringBuffer("helma.").append(name).append(
0276: ".access").toString());
0277: eventLogName = props.getProperty("eventLog", new StringBuffer(
0278: "helma.").append(name).append(".event").toString());
0279:
0280: // create app-level db sources
0281: dbProps = new ResourceProperties(this , "db.properties",
0282: sysDbProps, false);
0283:
0284: // the passwd file, to be used with the authenticate() function
0285: CryptResource parentpwfile = null;
0286:
0287: if (hopHome != null) {
0288: parentpwfile = new CryptResource(new FileResource(new File(
0289: hopHome, "passwd")), null);
0290: }
0291:
0292: pwfile = new CryptResource(repositories[0]
0293: .getResource("passwd"), parentpwfile);
0294:
0295: // the properties that map java class names to prototype names
0296: classMapping = new ResourceProperties(this , "class.properties");
0297: classMapping.setIgnoreCase(false);
0298:
0299: // get class name of root object if defined. Otherwise native Helma objectmodel will be used.
0300: rootObjectClass = classMapping.getProperty("root");
0301:
0302: updateProperties();
0303:
0304: dbSources = new Hashtable();
0305: modules = new SystemMap();
0306:
0307: cachenode = new TransientNode("app");
0308: }
0309:
0310: /**
0311: * Get the application ready to run, initializing the evaluators and type manager.
0312: */
0313: public synchronized void init() throws DatabaseException,
0314: IllegalAccessException, InstantiationException,
0315: ClassNotFoundException {
0316: init(null);
0317: }
0318:
0319: /**
0320: * Get the application ready to run, initializing the evaluators and type manager.
0321: *
0322: * @param ignoreDirs comma separated list of directory names to ignore
0323: */
0324: public synchronized void init(String ignoreDirs)
0325: throws DatabaseException, IllegalAccessException,
0326: InstantiationException, ClassNotFoundException {
0327:
0328: running = true;
0329:
0330: // create and init type mananger
0331: typemgr = new TypeManager(this , ignoreDirs);
0332: // set the context classloader. Note that this must be done before
0333: // using the logging framework so that a new LogFactory gets created
0334: // for this app.
0335: Thread.currentThread().setContextClassLoader(
0336: typemgr.getClassLoader());
0337: try {
0338: typemgr.createPrototypes();
0339: } catch (Exception x) {
0340: logError("Error creating prototypes", x);
0341: }
0342:
0343: if (Server.getServer() != null) {
0344: Vector extensions = Server.getServer().getExtensions();
0345:
0346: for (int i = 0; i < extensions.size(); i++) {
0347: HelmaExtension ext = (HelmaExtension) extensions.get(i);
0348:
0349: try {
0350: ext.applicationStarted(this );
0351: } catch (ConfigurationException e) {
0352: logEvent("couldn't init extension " + ext.getName()
0353: + ": " + e.toString());
0354: }
0355: }
0356: }
0357:
0358: // create and init evaluator/thread lists
0359: freeThreads = new Stack();
0360: allThreads = new Vector();
0361:
0362: // preallocate minThreads request evaluators
0363: int minThreads = 0;
0364:
0365: try {
0366: minThreads = Integer.parseInt(props
0367: .getProperty("minThreads"));
0368: } catch (Exception ignore) {
0369: // not parsable as number, keep 0
0370: }
0371:
0372: if (minThreads > 0) {
0373: logEvent("Starting " + minThreads + " evaluator(s) for "
0374: + name);
0375: }
0376:
0377: for (int i = 0; i < minThreads; i++) {
0378: RequestEvaluator ev = new RequestEvaluator(this );
0379:
0380: if (i == 0) {
0381: ev.initScriptingEngine();
0382: }
0383: freeThreads.push(ev);
0384: allThreads.addElement(ev);
0385: }
0386:
0387: activeRequests = new Hashtable();
0388: activeCronJobs = new Hashtable();
0389: customCronJobs = new Hashtable();
0390:
0391: // create the skin manager
0392: skinmgr = new SkinManager(this );
0393:
0394: // read in root id, root prototype, user prototype
0395: rootId = props.getProperty("rootid", "0");
0396: String rootPrototype = props.getProperty("rootprototype",
0397: "root");
0398: String userPrototype = props.getProperty("userprototype",
0399: "user");
0400:
0401: rootMapping = getDbMapping(rootPrototype);
0402: if (rootMapping == null)
0403: throw new RuntimeException("rootPrototype does not exist: "
0404: + rootPrototype);
0405: userMapping = getDbMapping(userPrototype);
0406: if (userMapping == null)
0407: throw new RuntimeException("userPrototype does not exist: "
0408: + userPrototype);
0409:
0410: // The whole user/userroot handling is basically old
0411: // ugly obsolete crap. Don't bother.
0412: ResourceProperties p = new ResourceProperties();
0413: String usernameField = (userMapping != null) ? userMapping
0414: .getNameField() : null;
0415:
0416: if (usernameField == null) {
0417: usernameField = "name";
0418: }
0419:
0420: p.put("_children", "collection(" + userPrototype + ")");
0421: p.put("_children.accessname", usernameField);
0422: userRootMapping = new DbMapping(this , "__userroot__", p);
0423: userRootMapping.update();
0424:
0425: // create the node manager
0426: nmgr = new NodeManager(this );
0427: nmgr.init(dbDir.getAbsoluteFile(), props);
0428:
0429: // create and init session manager
0430: String sessionMgrImpl = props.getProperty("sessionManagerImpl",
0431: "helma.framework.core.SessionManager");
0432: sessionMgr = (SessionManager) Class.forName(sessionMgrImpl)
0433: .newInstance();
0434: logEvent("Using session manager class " + sessionMgrImpl);
0435: sessionMgr.init(this );
0436:
0437: // read the sessions if wanted
0438: if ("true".equalsIgnoreCase(getProperty("persistentSessions"))) {
0439: RequestEvaluator ev = getEvaluator();
0440: try {
0441: ev.initScriptingEngine();
0442: sessionMgr.loadSessionData(null, ev.scriptingEngine);
0443: } finally {
0444: releaseEvaluator(ev);
0445: }
0446: }
0447:
0448: // reset the classloader to the parent/system/server classloader.
0449: Thread.currentThread().setContextClassLoader(
0450: typemgr.getClassLoader().getParent());
0451: }
0452:
0453: /**
0454: * Create and start scheduler and cleanup thread
0455: */
0456: public synchronized void start() {
0457: starttime = System.currentTimeMillis();
0458:
0459: // as first thing, invoke global onStart() function
0460: RequestEvaluator eval = null;
0461: try {
0462: eval = getEvaluator();
0463: eval.invokeInternal(null, "onStart",
0464: RequestEvaluator.EMPTY_ARGS);
0465: } catch (Exception xcept) {
0466: logError("Error in " + name + ".onStart()", xcept);
0467: } finally {
0468: releaseEvaluator(eval);
0469: }
0470:
0471: worker = new Thread(this , "Worker-" + name);
0472: worker.setPriority(Thread.NORM_PRIORITY + 1);
0473: worker.start();
0474: }
0475:
0476: /**
0477: * This is called to shut down a running application.
0478: */
0479: public synchronized void stop() {
0480: // invoke global onStop() function
0481: RequestEvaluator eval = null;
0482: try {
0483: eval = getEvaluator();
0484: eval.invokeInternal(null, "onStop",
0485: RequestEvaluator.EMPTY_ARGS);
0486: } catch (Exception x) {
0487: logError("Error in " + name + ".onStop()", x);
0488: }
0489:
0490: // mark app as stopped
0491: running = false;
0492:
0493: // stop all threads, this app is going down
0494: if (worker != null) {
0495: worker.interrupt();
0496: }
0497:
0498: worker = null;
0499:
0500: // stop evaluators
0501: if (allThreads != null) {
0502: for (Enumeration e = allThreads.elements(); e
0503: .hasMoreElements();) {
0504: RequestEvaluator ev = (RequestEvaluator) e
0505: .nextElement();
0506:
0507: ev.stopTransactor();
0508: }
0509: }
0510:
0511: // remove evaluators
0512: allThreads.removeAllElements();
0513: freeThreads.clear();
0514:
0515: // shut down node manager and embedded db
0516: try {
0517: nmgr.shutdown();
0518: } catch (DatabaseException dbx) {
0519: System.err.println("Error shutting down embedded db: "
0520: + dbx);
0521: }
0522:
0523: // tell the extensions that we're stopped.
0524: if (Server.getServer() != null) {
0525: Vector extensions = Server.getServer().getExtensions();
0526:
0527: for (int i = 0; i < extensions.size(); i++) {
0528: HelmaExtension ext = (HelmaExtension) extensions.get(i);
0529:
0530: ext.applicationStopped(this );
0531: }
0532: }
0533:
0534: // store the sessions if wanted
0535: if ("true".equalsIgnoreCase(getProperty("persistentSessions"))) {
0536: // sessionMgr.storeSessionData(null);
0537: sessionMgr.storeSessionData(null, eval.scriptingEngine);
0538: }
0539: sessionMgr.shutdown();
0540: }
0541:
0542: /**
0543: * Returns true if this app is currently running
0544: *
0545: * @return true if the app is running
0546: */
0547: public boolean isRunning() {
0548: return running;
0549: }
0550:
0551: /**
0552: * Get the application directory.
0553: *
0554: * @return the application directory, or first file based repository
0555: */
0556: public File getAppDir() {
0557: return appDir;
0558: }
0559:
0560: /**
0561: * Get a comparator for comparing Resources according to the order of
0562: * repositories they're contained in.
0563: *
0564: * @return a comparator that sorts resources according to their repositories
0565: */
0566: public ResourceComparator getResourceComparator() {
0567: return resourceComparator;
0568: }
0569:
0570: /**
0571: * Returns a free evaluator to handle a request.
0572: */
0573: public RequestEvaluator getEvaluator() {
0574: if (!running) {
0575: throw new ApplicationStoppedException();
0576: }
0577:
0578: // first try
0579: try {
0580: return (RequestEvaluator) freeThreads.pop();
0581: } catch (EmptyStackException nothreads) {
0582: int maxThreads = 50;
0583:
0584: String maxThreadsProp = props.getProperty("maxThreads");
0585: if (maxThreadsProp != null) {
0586: try {
0587: maxThreads = Integer.parseInt(maxThreadsProp);
0588: } catch (Exception ignore) {
0589: logEvent("Couldn't parse maxThreads property: "
0590: + maxThreadsProp);
0591: }
0592: }
0593:
0594: synchronized (this ) {
0595: // allocate a new evaluator
0596: if (allThreads.size() < maxThreads) {
0597: logEvent("Starting engine "
0598: + (allThreads.size() + 1) + " for " + name);
0599:
0600: RequestEvaluator ev = new RequestEvaluator(this );
0601:
0602: allThreads.addElement(ev);
0603:
0604: return (ev);
0605: }
0606: }
0607: }
0608:
0609: // we can't create a new evaluator, so we wait if one becomes available.
0610: // give it 3 more tries, waiting 3 seconds each time.
0611: for (int i = 0; i < 4; i++) {
0612: try {
0613: Thread.sleep(3000);
0614:
0615: return (RequestEvaluator) freeThreads.pop();
0616: } catch (EmptyStackException nothreads) {
0617: // do nothing
0618: } catch (InterruptedException inter) {
0619: throw new RuntimeException("Thread interrupted.");
0620: }
0621: }
0622:
0623: // no luck, give up.
0624: throw new RuntimeException("Maximum Thread count reached.");
0625: }
0626:
0627: /**
0628: * Returns an evaluator back to the pool when the work is done.
0629: */
0630: public void releaseEvaluator(RequestEvaluator ev) {
0631: if (ev != null) {
0632: ev.recycle();
0633: freeThreads.push(ev);
0634: }
0635: }
0636:
0637: /**
0638: * This can be used to set the maximum number of evaluators which will be allocated.
0639: * If evaluators are required beyound this number, an error will be thrown.
0640: */
0641: public boolean setNumberOfEvaluators(int n) {
0642: if ((n < 2) || (n > 511)) {
0643: return false;
0644: }
0645:
0646: int current = allThreads.size();
0647:
0648: synchronized (allThreads) {
0649: if (n > current) {
0650: int toBeCreated = n - current;
0651:
0652: for (int i = 0; i < toBeCreated; i++) {
0653: RequestEvaluator ev = new RequestEvaluator(this );
0654:
0655: freeThreads.push(ev);
0656: allThreads.addElement(ev);
0657: }
0658: } else if (n < current) {
0659: int toBeDestroyed = current - n;
0660:
0661: for (int i = 0; i < toBeDestroyed; i++) {
0662: try {
0663: RequestEvaluator re = (RequestEvaluator) freeThreads
0664: .pop();
0665: allThreads.removeElement(re);
0666: re.stopTransactor();
0667: } catch (EmptyStackException empty) {
0668: return false;
0669: }
0670: }
0671: }
0672: }
0673:
0674: return true;
0675: }
0676:
0677: /**
0678: * Return the number of currently active threads
0679: */
0680: public int getActiveThreads() {
0681: return 0;
0682: }
0683:
0684: /**
0685: * Execute a request coming in from a web client.
0686: */
0687: public ResponseTrans execute(RequestTrans req) {
0688: requestCount += 1;
0689:
0690: // get user for this request's session
0691: Session session = createSession(req.getSession());
0692: session.touch();
0693:
0694: ResponseTrans res = null;
0695: RequestEvaluator ev = null;
0696:
0697: // are we responsible for releasing the evaluator and closing the result?
0698: boolean primaryRequest = false;
0699:
0700: try {
0701: // first look if a request with same user/path/data is already being executed.
0702: // if so, attach the request to its output instead of starting a new evaluation
0703: // this helps to cleanly solve "doubleclick" kind of users
0704: ev = (RequestEvaluator) activeRequests.get(req);
0705:
0706: if (ev != null) {
0707: res = ev.attachHttpRequest(req);
0708: }
0709:
0710: if (res == null) {
0711: primaryRequest = true;
0712:
0713: // if attachHttpRequest returns null this means we came too late
0714: // and the other request was finished in the meantime
0715: // check if the properties file has been updated
0716: updateProperties();
0717:
0718: // get evaluator and invoke
0719: ev = getEvaluator();
0720: res = ev.invokeHttp(req, session);
0721: }
0722: } catch (ApplicationStoppedException stopped) {
0723: // let the servlet know that this application has gone to heaven
0724: throw stopped;
0725: } catch (Exception x) {
0726: errorCount += 1;
0727: res = new ResponseTrans(this , req);
0728: res.reportError(name, x.getMessage());
0729: } finally {
0730: if (primaryRequest) {
0731: activeRequests.remove(req);
0732: releaseEvaluator(ev);
0733:
0734: // response needs to be closed/encoded before sending it back
0735: try {
0736: res.close(charset);
0737: } catch (UnsupportedEncodingException uee) {
0738: logError("Unsupported response encoding", uee);
0739: }
0740: } else {
0741: res.waitForClose();
0742: }
0743: }
0744:
0745: return res;
0746: }
0747:
0748: /**
0749: * Called to execute a method via XML-RPC, usally by helma.main.ApplicationManager
0750: * which acts as default handler/request dispatcher.
0751: */
0752: public Object executeXmlRpc(String method, Vector args)
0753: throws Exception {
0754: xmlrpcCount += 1;
0755:
0756: Object retval = null;
0757: RequestEvaluator ev = null;
0758:
0759: try {
0760: // check if the properties file has been updated
0761: updateProperties();
0762:
0763: // get evaluator and invoke
0764: ev = getEvaluator();
0765: retval = ev.invokeXmlRpc(method, args.toArray());
0766: } finally {
0767: releaseEvaluator(ev);
0768: }
0769:
0770: return retval;
0771: }
0772:
0773: public Object executeExternal(String method, Vector args)
0774: throws Exception {
0775: Object retval = null;
0776: RequestEvaluator ev = null;
0777: try {
0778: // check if the properties file has been updated
0779: updateProperties();
0780: // get evaluator and invoke
0781: ev = getEvaluator();
0782: retval = ev.invokeExternal(method, args.toArray());
0783: } finally {
0784: releaseEvaluator(ev);
0785: }
0786: return retval;
0787: }
0788:
0789: /**
0790: * Reset the application's object cache, causing all objects to be refetched from
0791: * the database.
0792: */
0793: public void clearCache() {
0794: nmgr.clearCache();
0795: }
0796:
0797: /**
0798: * Returns the number of elements in the NodeManager's cache
0799: */
0800: public int getCacheUsage() {
0801: return nmgr.countCacheEntries();
0802: }
0803:
0804: /**
0805: * Set the application's root element to an arbitrary object. After this is called
0806: * with a non-null object, the helma node manager will be bypassed. This function
0807: * can be used to script and publish any Java object structure with Helma.
0808: */
0809: public void setDataRoot(Object root) {
0810: this .rootObject = root;
0811: }
0812:
0813: /**
0814: * This method returns the root object of this application's object tree.
0815: */
0816: public Object getDataRoot() {
0817: // check if we have a custom root object class
0818: if (rootObjectClass != null) {
0819: // create custom root element.
0820: if (rootObject == null) {
0821: try {
0822: if (classMapping.containsKey("root.factory.class")
0823: && classMapping
0824: .containsKey("root.factory.method")) {
0825: String rootFactory = classMapping
0826: .getProperty("root.factory.class");
0827: Class c = typemgr.getClassLoader().loadClass(
0828: rootFactory);
0829: Method m = c.getMethod(classMapping
0830: .getProperty("root.factory.method"),
0831: (Class[]) null);
0832:
0833: rootObject = m.invoke(c, (Object[]) null);
0834: } else {
0835: String rootClass = classMapping
0836: .getProperty("root");
0837: Class c = typemgr.getClassLoader().loadClass(
0838: rootClass);
0839:
0840: rootObject = c.newInstance();
0841: }
0842: } catch (Exception e) {
0843: throw new RuntimeException(
0844: "Error creating root object: "
0845: + e.toString());
0846: }
0847: }
0848:
0849: return rootObject;
0850: }
0851: // no custom root object is defined - use standard helma objectmodel
0852: else {
0853: return nmgr.safe.getRootNode();
0854: }
0855: }
0856:
0857: /**
0858: * Return the prototype of the object to be used as this application's root object
0859: */
0860: public DbMapping getRootMapping() {
0861: return rootMapping;
0862: }
0863:
0864: /**
0865: * Return the id of the object to be used as this application's root object
0866: */
0867: public String getRootId() {
0868: return rootId;
0869: }
0870:
0871: /**
0872: * Returns the Object which contains registered users of this application.
0873: */
0874: public INode getUserRoot() {
0875: INode users = nmgr.safe.getNode("1", userRootMapping);
0876:
0877: users.setDbMapping(userRootMapping);
0878:
0879: return users;
0880: }
0881:
0882: /**
0883: * Returns the node manager for this application. The node manager is
0884: * the gateway to the helma.objectmodel packages, which perform the mapping
0885: * of objects to relational database tables or the embedded database.
0886: */
0887: public NodeManager getNodeManager() {
0888: return nmgr;
0889: }
0890:
0891: /**
0892: * Returns a wrapper containing the node manager for this application. The node manager is
0893: * the gateway to the helma.objectmodel packages, which perform the mapping of objects to
0894: * relational database tables or the embedded database.
0895: */
0896: public WrappedNodeManager getWrappedNodeManager() {
0897: return nmgr.safe;
0898: }
0899:
0900: /**
0901: * Return the application's session manager
0902: * @return the SessionManager instance used by this app
0903: */
0904: public SessionManager getSessionManager() {
0905: return sessionMgr;
0906: }
0907:
0908: /**
0909: * Return a transient node that is shared by all evaluators of this application ("app node")
0910: */
0911: public INode getCacheNode() {
0912: return cachenode;
0913: }
0914:
0915: /**
0916: * Returns a Node representing a registered user of this application by his or her user name.
0917: */
0918: public INode getUserNode(String uid) {
0919: try {
0920: INode users = getUserRoot();
0921:
0922: return (INode) users.getChildElement(uid);
0923: } catch (Exception x) {
0924: return null;
0925: }
0926: }
0927:
0928: /**
0929: * Return a prototype for a given node. If the node doesn't specify a prototype,
0930: * return the generic hopobject prototype.
0931: */
0932: public Prototype getPrototype(Object obj) {
0933: String protoname = getPrototypeName(obj);
0934:
0935: if (protoname == null) {
0936: return typemgr.getPrototype("hopobject");
0937: }
0938:
0939: Prototype p = typemgr.getPrototype(protoname);
0940:
0941: if (p == null) {
0942: p = typemgr.getPrototype("hopobject");
0943: }
0944:
0945: return p;
0946: }
0947:
0948: /**
0949: * Return the prototype with the given name, if it exists
0950: */
0951: public Prototype getPrototypeByName(String name) {
0952: return typemgr.getPrototype(name);
0953: }
0954:
0955: /**
0956: * Return a collection containing all prototypes defined for this application
0957: */
0958: public Collection getPrototypes() {
0959: return typemgr.getPrototypes();
0960: }
0961:
0962: /**
0963: * Return a skin for a given object. The skin is found by determining the prototype
0964: * to use for the object, then looking up the skin for the prototype.
0965: */
0966: public Skin getSkin(String protoname, String skinname,
0967: Object[] skinpath) throws IOException {
0968: Prototype proto = getPrototypeByName(protoname);
0969:
0970: if (proto == null) {
0971: return null;
0972: }
0973:
0974: return skinmgr.getSkin(proto, skinname, skinpath);
0975: }
0976:
0977: /**
0978: * Return the session currently associated with a given Hop session ID.
0979: * Create a new session if necessary.
0980: */
0981: public Session createSession(String sessionId) {
0982: return sessionMgr.createSession(sessionId);
0983: }
0984:
0985: /**
0986: * Return a list of Helma nodes (HopObjects - the database object representing the user,
0987: * not the session object) representing currently logged in users.
0988: */
0989: public List getActiveUsers() {
0990: return sessionMgr.getActiveUsers();
0991: }
0992:
0993: /**
0994: * Return a list of Helma nodes (HopObjects - the database object representing the user,
0995: * not the session object) representing registered users of this application.
0996: */
0997: public List getRegisteredUsers() {
0998: ArrayList list = new ArrayList();
0999: INode users = getUserRoot();
1000:
1001: // add all child nodes to the list
1002: for (Enumeration e = users.getSubnodes(); e.hasMoreElements();) {
1003: list.add(e.nextElement());
1004: }
1005:
1006: // if none, try to get them from properties (internal db)
1007: if (list.size() == 0) {
1008: for (Enumeration e = users.properties(); e
1009: .hasMoreElements();) {
1010: list.add(users.getNode((String) e.nextElement()));
1011: }
1012: }
1013:
1014: return list;
1015: }
1016:
1017: /**
1018: * Return an array of <code>SessionBean</code> objects currently associated
1019: * with a given Helma user.
1020: */
1021: public List getSessionsForUsername(String username) {
1022: return sessionMgr.getSessionsForUsername(username);
1023: }
1024:
1025: /**
1026: * Return the session currently associated with a given Hop session ID.
1027: */
1028: public Session getSession(String sessionId) {
1029: return sessionMgr.getSession(sessionId);
1030: }
1031:
1032: /**
1033: * Return the whole session map.
1034: */
1035: public Map getSessions() {
1036: return sessionMgr.getSessions();
1037: }
1038:
1039: /**
1040: * Returns the number of currenty active sessions.
1041: */
1042: public int countSessions() {
1043: return sessionMgr.countSessions();
1044: }
1045:
1046: /**
1047: * Register a user with the given user name and password.
1048: */
1049: public INode registerUser(String uname, String password) {
1050: if (uname == null) {
1051: return null;
1052: }
1053:
1054: uname = uname.toLowerCase().trim();
1055:
1056: if ("".equals(uname)) {
1057: return null;
1058: }
1059:
1060: INode unode;
1061:
1062: try {
1063: INode users = getUserRoot();
1064:
1065: // check if a user with this name is already registered
1066: unode = (INode) users.getChildElement(uname);
1067: if (unode != null) {
1068: return null;
1069: }
1070:
1071: unode = new Node(uname, "user", nmgr.safe);
1072:
1073: String usernameField = (userMapping != null) ? userMapping
1074: .getNameField() : null;
1075: String usernameProp = null;
1076:
1077: if (usernameField != null) {
1078: usernameProp = userMapping
1079: .columnNameToProperty(usernameField);
1080: }
1081:
1082: if (usernameProp == null) {
1083: usernameProp = "name";
1084: }
1085:
1086: unode.setName(uname);
1087: unode.setString(usernameProp, uname);
1088: unode.setString("password", password);
1089:
1090: return users.addNode(unode);
1091:
1092: } catch (Exception x) {
1093: logEvent("Error registering User: " + x);
1094:
1095: return null;
1096: }
1097: }
1098:
1099: /**
1100: * Log in a user given his or her user name and password.
1101: */
1102: public boolean loginSession(String uname, String password,
1103: Session session) {
1104: // Check the name/password of a user and log it in to the current session
1105: if (uname == null) {
1106: return false;
1107: }
1108:
1109: uname = uname.toLowerCase().trim();
1110:
1111: if ("".equals(uname)) {
1112: return false;
1113: }
1114:
1115: try {
1116: INode users = getUserRoot();
1117: Node unode = (Node) users.getChildElement(uname);
1118: if (unode == null)
1119: return false;
1120:
1121: String pw = unode.getString("password");
1122:
1123: if ((pw != null) && pw.equals(password)) {
1124: // let the old user-object forget about this session
1125: session.logout();
1126: session.login(unode);
1127:
1128: return true;
1129: }
1130: } catch (Exception x) {
1131: return false;
1132: }
1133:
1134: return false;
1135: }
1136:
1137: /**
1138: * Log out a session from this application.
1139: */
1140: public void logoutSession(Session session) {
1141: session.logout();
1142: }
1143:
1144: /**
1145: * In contrast to login, this works outside the Hop user object framework. Instead, the user is
1146: * authenticated against a passwd file in the application directory. This is to have some sort of
1147: * authentication available *before* the application is up and running, i.e. for application setup tasks.
1148: */
1149: public boolean authenticate(String uname, String password) {
1150: if ((uname == null) || (password == null)) {
1151: return false;
1152: }
1153:
1154: return pwfile.authenticate(uname, password);
1155: }
1156:
1157: /**
1158: * Return the href to the root of this application.
1159: */
1160: public String getRootHref() throws UnsupportedEncodingException {
1161: return getNodeHref(getDataRoot(), null);
1162: }
1163:
1164: /**
1165: * Return a path to be used in a URL pointing to the given element and action
1166: */
1167: public String getNodeHref(Object elem, String actionName)
1168: throws UnsupportedEncodingException {
1169: StringBuffer b = new StringBuffer(baseURI);
1170:
1171: composeHref(elem, b, 0);
1172:
1173: if (actionName != null) {
1174: b.append(UrlEncoded.encode(actionName, charset));
1175: }
1176:
1177: return b.toString();
1178: }
1179:
1180: private void composeHref(Object elem, StringBuffer b, int pathCount)
1181: throws UnsupportedEncodingException {
1182: if ((elem == null) || (pathCount > 50)) {
1183: return;
1184: }
1185:
1186: if ((hrefRootPrototype != null)
1187: && hrefRootPrototype.equals(getPrototypeName(elem))) {
1188: return;
1189: }
1190:
1191: Object parent = getParentElement(elem);
1192:
1193: if (parent == null) {
1194: return;
1195: }
1196:
1197: // recurse to parent element
1198: composeHref(getParentElement(elem), b, ++pathCount);
1199:
1200: // append ourselves
1201: String ename = getElementName(elem);
1202: if (ename != null) {
1203: b.append(UrlEncoded.encode(ename, charset));
1204: b.append("/");
1205: }
1206: }
1207:
1208: /**
1209: * Returns the baseURI for Hrefs in this application.
1210: */
1211: public String getBaseURI() {
1212: return baseURI;
1213: }
1214:
1215: /**
1216: * This method sets the base URL of this application which will be prepended to
1217: * the actual object path.
1218: */
1219: public void setBaseURI(String uri) {
1220: if (uri == null) {
1221: this .baseURI = "/";
1222: } else if (!uri.endsWith("/")) {
1223: this .baseURI = uri + "/";
1224: } else {
1225: this .baseURI = uri;
1226: }
1227: }
1228:
1229: /**
1230: * Return true if the baseURI property is defined in the application
1231: * properties, false otherwise.
1232: */
1233: public boolean hasExplicitBaseURI() {
1234: return props.containsKey("baseuri");
1235: }
1236:
1237: /**
1238: * Returns the prototype name that Hrefs in this application should
1239: * start with.
1240: */
1241: public String getHrefRootPrototype() {
1242: return hrefRootPrototype;
1243: }
1244:
1245: /**
1246: * Tell other classes whether they should output logging information for
1247: * this application.
1248: */
1249: public boolean debug() {
1250: return debug;
1251: }
1252:
1253: /**
1254: * Get the current RequestEvaluator, or null if the calling thread
1255: * is not evaluating a request.
1256: *
1257: * @return the RequestEvaluator belonging to the current thread
1258: */
1259: public RequestEvaluator getCurrentRequestEvaluator() {
1260: return (RequestEvaluator) currentEvaluator.get();
1261: }
1262:
1263: /**
1264: * Set the current RequestEvaluator for the calling thread.
1265: * @param eval the RequestEvaluator belonging to the current thread
1266: */
1267: protected void setCurrentRequestEvaluator(RequestEvaluator eval) {
1268: currentEvaluator.set(eval);
1269: }
1270:
1271: /**
1272: * Utility function invoker for the methods below. This *must* be called
1273: * by an active RequestEvaluator thread.
1274: */
1275: private Object invokeFunction(Object obj, String func, Object[] args) {
1276: RequestEvaluator reval = getCurrentRequestEvaluator();
1277: if (reval != null) {
1278: if (args == null) {
1279: args = RequestEvaluator.EMPTY_ARGS;
1280: }
1281: try {
1282: return reval.invokeDirectFunction(obj, func, args);
1283: } catch (Exception x) {
1284: if (debug) {
1285: System.err
1286: .println("Error in Application.invokeFunction ("
1287: + func + "): " + x);
1288: }
1289: }
1290: }
1291: return null;
1292: }
1293:
1294: /**
1295: * Return the application's classloader
1296: */
1297: public ClassLoader getClassLoader() {
1298: return typemgr.getClassLoader();
1299: }
1300:
1301: //////////////////////////////////////////////////////////////////////////////////////////////////////////
1302: /// The following methods mimic the IPathElement interface. This allows us
1303: /// to script any Java object: If the object implements IPathElement (as does
1304: /// the Node class in Helma's internal objectmodel) then the corresponding
1305: /// method is called in the object itself. Otherwise, a corresponding script function
1306: /// is called on the object.
1307: //////////////////////////////////////////////////////////////////////////////////////////////////////////
1308:
1309: /**
1310: * Return the name to be used to get this element from its parent
1311: */
1312: public String getElementName(Object obj) {
1313: if (obj instanceof IPathElement) {
1314: return ((IPathElement) obj).getElementName();
1315: }
1316:
1317: Object retval = invokeFunction(obj, "getElementName",
1318: RequestEvaluator.EMPTY_ARGS);
1319:
1320: if (retval != null) {
1321: return retval.toString();
1322: }
1323:
1324: return null;
1325: }
1326:
1327: /**
1328: * Retrieve a child element of this object by name.
1329: */
1330: public Object getChildElement(Object obj, String name) {
1331: if (obj instanceof IPathElement) {
1332: return ((IPathElement) obj).getChildElement(name);
1333: }
1334:
1335: Object[] arg = new Object[1];
1336:
1337: arg[0] = name;
1338:
1339: return invokeFunction(obj, "getChildElement", arg);
1340: }
1341:
1342: /**
1343: * Return the parent element of this object.
1344: */
1345: public Object getParentElement(Object obj) {
1346: if (obj instanceof IPathElement) {
1347: return ((IPathElement) obj).getParentElement();
1348: }
1349:
1350: return invokeFunction(obj, "getParentElement",
1351: RequestEvaluator.EMPTY_ARGS);
1352: }
1353:
1354: /**
1355: * Get the name of the prototype to be used for this object. This will
1356: * determine which scripts, actions and skins can be called on it
1357: * within the Helma scripting and rendering framework.
1358: */
1359: public String getPrototypeName(Object obj) {
1360: if (obj == null) {
1361: return "global";
1362: } else if (obj instanceof IPathElement) {
1363: // check if e implements the IPathElement interface
1364: return ((IPathElement) obj).getPrototype();
1365: }
1366:
1367: Class clazz = obj.getClass();
1368: String className = clazz.getName();
1369: String protoName = classMapping.getProperty(className);
1370: if (protoName != null) {
1371: return protoName == CLASS_NOT_MAPPED ? null : protoName;
1372: }
1373:
1374: // walk down superclass path
1375: while ((clazz = clazz.getSuperclass()) != null) {
1376: protoName = classMapping.getProperty(clazz.getName());
1377: if (protoName != null) {
1378: // cache the class name for the object so we run faster next time
1379: classMapping.setProperty(className, protoName);
1380: return protoName;
1381: }
1382: }
1383: // check interfaces, too
1384: Class[] classes = obj.getClass().getInterfaces();
1385: for (int i = 0; i < classes.length; i++) {
1386: protoName = classMapping.getProperty(classes[i].getName());
1387: if (protoName != null) {
1388: // cache the class name for the object so we run faster next time
1389: classMapping.setProperty(className, protoName);
1390: return protoName;
1391: }
1392: }
1393: // not mapped - cache negative result
1394: classMapping.setProperty(className, CLASS_NOT_MAPPED);
1395: return null;
1396: }
1397:
1398: public DocApplication getDoc() {
1399: RequestEvaluator eval = null;
1400: try {
1401: eval = getEvaluator();
1402: return eval.scriptingEngine.getDoc();
1403: } catch (Exception xcept) {
1404: logError("Error in getDoc() for " + name, xcept);
1405: return null;
1406: } finally {
1407: releaseEvaluator(eval);
1408: }
1409: }
1410:
1411: ////////////////////////////////////////////////////////////////////////
1412:
1413: /**
1414: * Log an application error
1415: */
1416: public void logError(String msg, Throwable error) {
1417: if (eventLog == null) {
1418: eventLog = getLogger(eventLogName);
1419: }
1420: eventLog.error(msg, error);
1421: }
1422:
1423: /**
1424: * Log an application error
1425: */
1426: public void logError(String msg) {
1427: if (eventLog == null) {
1428: eventLog = getLogger(eventLogName);
1429: }
1430: eventLog.error(msg);
1431: }
1432:
1433: /**
1434: * Log a generic application event
1435: */
1436: public void logEvent(String msg) {
1437: getEventLog().info(msg);
1438: }
1439:
1440: /**
1441: * Log an application access
1442: */
1443: public void logAccess(String msg) {
1444: getAccessLog().info(msg);
1445: }
1446:
1447: /**
1448: * get the app's event log.
1449: */
1450: Log getEventLog() {
1451: if (eventLog == null) {
1452: eventLog = getLogger(eventLogName);
1453: // set log level for event log in case it is a helma.util.Logger
1454: if (eventLog instanceof Logger) {
1455: if (debug && !eventLog.isDebugEnabled())
1456: ((Logger) eventLog).setLogLevel(Logger.DEBUG);
1457: else if (!eventLog.isInfoEnabled())
1458: ((Logger) eventLog).setLogLevel(Logger.INFO);
1459: }
1460: }
1461: return eventLog;
1462: }
1463:
1464: /**
1465: * get the app's access log.
1466: */
1467: Log getAccessLog() {
1468: if (accessLog == null) {
1469: accessLog = getLogger(accessLogName);
1470: }
1471: return accessLog;
1472: }
1473:
1474: /**
1475: * Get a logger object to log events for this application.
1476: */
1477: public Log getLogger(String logname) {
1478: if ("console".equals(logDir) || "console".equals(logname)) {
1479: return Logging.getConsoleLog();
1480: } else {
1481: return LogFactory.getLog(logname);
1482: }
1483: }
1484:
1485: /**
1486: * The run method performs periodic tasks like executing the scheduler method and
1487: * kicking out expired user sessions.
1488: */
1489: public void run() {
1490:
1491: // interval between session cleanups
1492: long lastSessionCleanup = System.currentTimeMillis();
1493:
1494: while (Thread.currentThread() == worker) {
1495:
1496: try {
1497: // interval between scheduler runs
1498: long sleepInterval = 60000;
1499:
1500: try {
1501: String sleepProp = props
1502: .getProperty("schedulerInterval");
1503: if (sleepProp != null) {
1504: sleepInterval = Math.max(1000, Integer
1505: .parseInt(sleepProp) * 1000);
1506: } else {
1507: sleepInterval = CronJob
1508: .millisToNextFullMinute();
1509: }
1510: } catch (Exception ignore) {
1511: // we'll use the default interval
1512: }
1513:
1514: // sleep until the next full minute
1515: try {
1516: Thread.sleep(sleepInterval);
1517: } catch (InterruptedException x) {
1518: worker = null;
1519: break;
1520: }
1521:
1522: // purge sessions
1523: try {
1524: lastSessionCleanup = sessionMgr
1525: .cleanupSessions(lastSessionCleanup);
1526: } catch (Exception x) {
1527: logError("Error in session cleanup: " + x, x);
1528: } catch (LinkageError x) {
1529: logError("Error in session cleanup: " + x, x);
1530: }
1531:
1532: // execute cron jobs
1533: try {
1534: executeCronJobs();
1535: } catch (Exception x) {
1536: logError("Error in cron job execution: " + x, x);
1537: } catch (LinkageError x) {
1538: logError("Error in cron job execution: " + x, x);
1539: }
1540:
1541: } catch (VirtualMachineError error) {
1542: logError("Error in scheduler loop: " + error, error);
1543: }
1544: }
1545:
1546: // when interrupted, shutdown running cron jobs
1547: synchronized (activeCronJobs) {
1548: for (Iterator i = activeCronJobs.values().iterator(); i
1549: .hasNext();) {
1550: ((CronRunner) i.next()).interrupt();
1551: i.remove();
1552: }
1553: }
1554:
1555: logEvent("Scheduler for " + name + " exiting");
1556: }
1557:
1558: /**
1559: * Executes cron jobs for the application, which are either
1560: * defined in app.properties or via app.addCronJob().
1561: * This method is called by run().
1562: */
1563: private void executeCronJobs() {
1564: // loop-local cron job data
1565: List jobs = CronJob.parse(props.getSubProperties("cron."));
1566: Date date = new Date();
1567:
1568: jobs.addAll(customCronJobs.values());
1569: CronJob.sort(jobs);
1570:
1571: if (debug) {
1572: logEvent("Running cron jobs: " + jobs);
1573: }
1574: if (!activeCronJobs.isEmpty()) {
1575: logEvent("Cron jobs still running from last minute: "
1576: + activeCronJobs);
1577: }
1578:
1579: for (Iterator i = jobs.iterator(); i.hasNext();) {
1580: CronJob job = (CronJob) i.next();
1581:
1582: if (job.appliesToDate(date)) {
1583: // check if the job is already active ...
1584: if (activeCronJobs.containsKey(job.getName())) {
1585: logEvent(job
1586: + " is still active, skipped in this minute");
1587:
1588: continue;
1589: }
1590:
1591: RequestEvaluator evaluator;
1592:
1593: try {
1594: evaluator = getEvaluator();
1595: } catch (RuntimeException rt) {
1596: if (running) {
1597: logEvent("couldn't execute " + job
1598: + ", maximum thread count reached");
1599:
1600: continue;
1601: } else {
1602: break;
1603: }
1604: }
1605:
1606: // if the job has a long timeout or we're already late during this minute
1607: // the job is run from an extra thread
1608: if ((job.getTimeout() > 20000)
1609: || (CronJob.millisToNextFullMinute() < 30000)) {
1610: CronRunner runner = new CronRunner(evaluator, job);
1611:
1612: activeCronJobs.put(job.getName(), runner);
1613: runner.start();
1614: } else {
1615: try {
1616: evaluator.invokeInternal(null, job
1617: .getFunction(),
1618: RequestEvaluator.EMPTY_ARGS, job
1619: .getTimeout());
1620: } catch (Exception ex) {
1621: logEvent("error running " + job + ": " + ex);
1622: } finally {
1623: releaseEvaluator(evaluator);
1624: }
1625: }
1626: }
1627: }
1628: }
1629:
1630: /**
1631: * Check whether a prototype is for scripting a java class, i.e. if there's an entry
1632: * for it in the class.properties file.
1633: */
1634: public boolean isJavaPrototype(String typename) {
1635: return classMapping.contains(typename);
1636: }
1637:
1638: /**
1639: * Return the java class that a given prototype wraps, or null.
1640: */
1641: public String getJavaClassForPrototype(String typename) {
1642:
1643: for (Iterator it = classMapping.entrySet().iterator(); it
1644: .hasNext();) {
1645: Map.Entry entry = (Map.Entry) it.next();
1646:
1647: if (typename.equals(entry.getValue())) {
1648: return (String) entry.getKey();
1649: }
1650: }
1651:
1652: return null;
1653: }
1654:
1655: /**
1656: * Return a DbSource object for a given name. A DbSource is a relational database defined
1657: * in a db.properties file.
1658: */
1659: public DbSource getDbSource(String name) {
1660: String dbSrcName = name.toLowerCase();
1661: DbSource dbs = (DbSource) dbSources.get(dbSrcName);
1662:
1663: if (dbs != null) {
1664: return dbs;
1665: }
1666:
1667: try {
1668: dbs = new DbSource(name, dbProps);
1669: dbSources.put(dbSrcName, dbs);
1670: } catch (Exception problem) {
1671: logEvent("Error creating DbSource " + name + ": ");
1672: logEvent(problem.toString());
1673: }
1674:
1675: return dbs;
1676: }
1677:
1678: /**
1679: * Return the name of this application
1680: */
1681: public String getName() {
1682: return name;
1683: }
1684:
1685: /**
1686: * Add a repository to this app's repository list. This is used for
1687: * ZipRepositories contained in top-level file repositories, for instance.
1688: *
1689: * @param rep the repository to add
1690: * @return if the repository was not yet contained
1691: */
1692: public boolean addRepository(Repository rep) {
1693: if (rep != null && !repositories.contains(rep)) {
1694: // Add the new repository before its parent repository.
1695: // This establishes the order of compilation between FileRepositories
1696: // and embedded ZipRepositories.
1697: Repository parent = rep.getParentRepository();
1698: if (parent != null) {
1699: int idx = repositories.indexOf(parent);
1700: if (idx > -1) {
1701: repositories.add(idx, rep);
1702: return true;
1703: }
1704: }
1705: // no parent or parent not in app's repositories, add at end of list.
1706: repositories.add(rep);
1707: return true;
1708: }
1709: return false;
1710: }
1711:
1712: /**
1713: * Searches for the index of the given repository for this app.
1714: * The arguement must be a root argument, or -1 will be returned.
1715: *
1716: * @param rep one of this app's root repositories.
1717: * @return the index of the first occurrence of the argument in this
1718: * list; returns <tt>-1</tt> if the object is not found.
1719: */
1720: public int getRepositoryIndex(Repository rep) {
1721: return repositories.indexOf(rep);
1722: }
1723:
1724: /**
1725: * Returns the repositories of this application
1726: * @return iterator through application repositories
1727: */
1728: public List getRepositories() {
1729: return Collections.unmodifiableList(repositories);
1730: }
1731:
1732: /**
1733: * Set the code resource currently being evaluated/compiled. This is used
1734: * to set the proper parent repository when a new repository is added
1735: * via app.addRepository().
1736: *
1737: * @param resource the resource being currently evaluated/compiled
1738: */
1739: public void setCurrentCodeResource(Resource resource) {
1740: currentCodeResource = resource;
1741: }
1742:
1743: /**
1744: * Set the code resource currently being evaluated/compiled. This is used
1745: * to set the proper parent repository when a new repository is added
1746: * via app.addRepository().
1747:
1748: * @return the resource being currently evaluated/compiled
1749: */
1750: public Resource getCurrentCodeResource() {
1751: return currentCodeResource;
1752: }
1753:
1754: /**
1755: * Return the directory of the Helma server
1756: */
1757: public File getServerDir() {
1758: return hopHome;
1759: }
1760:
1761: /**
1762: * Get the DbMapping associated with a prototype name in this application
1763: */
1764: public DbMapping getDbMapping(String typename) {
1765: Prototype proto = typemgr.getPrototype(typename);
1766:
1767: if (proto == null) {
1768: return null;
1769: }
1770:
1771: return proto.getDbMapping();
1772: }
1773:
1774: /**
1775: * Return the current upload status.
1776: * @param req the upload RequestTrans
1777: * @return the current upload status.
1778: */
1779: public UploadStatus getUploadStatus(RequestTrans req) {
1780: String uploadId = (String) req.get("upload_id");
1781: if (uploadId == null)
1782: return null;
1783:
1784: String sessionId = req.getSession();
1785: Session session = getSession(sessionId);
1786: if (session == null)
1787: return null;
1788: return session.createUpload(uploadId);
1789: }
1790:
1791: private synchronized void updateProperties() {
1792: // if so property file has been updated, re-read props.
1793: if (props.lastModified() > lastPropertyRead) {
1794: // force property update
1795: props.update();
1796:
1797: // character encoding to be used for responses
1798: charset = props.getProperty("charset", "ISO-8859-1");
1799:
1800: // debug flag
1801: debug = "true".equalsIgnoreCase(props.getProperty("debug"));
1802:
1803: // if rhino debugger is enabled use higher (10 min) default request timeout
1804: String defaultReqTimeout = "true".equalsIgnoreCase(props
1805: .getProperty("rhino.debug")) ? "600" : "60";
1806: String reqTimeout = props.getProperty("requesttimeout",
1807: defaultReqTimeout);
1808: try {
1809: requestTimeout = Long.parseLong(reqTimeout) * 1000L;
1810: } catch (Exception ignore) {
1811: // go with default value
1812: requestTimeout = 60000L;
1813: }
1814:
1815: // set base URI
1816: String base = props.getProperty("baseuri");
1817:
1818: if (base != null) {
1819: setBaseURI(base);
1820: } else if (baseURI == null) {
1821: baseURI = "/";
1822: }
1823:
1824: hrefRootPrototype = props.getProperty("hrefrootprototype");
1825:
1826: // update the XML-RPC access list, containting prototype.method
1827: // entries of functions that may be called via XML-RPC
1828: String xmlrpcAccessProp = props.getProperty("xmlrpcaccess");
1829: HashSet xra = new HashSet();
1830:
1831: if (xmlrpcAccessProp != null) {
1832: StringTokenizer st = new StringTokenizer(
1833: xmlrpcAccessProp, ",; ");
1834:
1835: while (st.hasMoreTokens()) {
1836: String token = st.nextToken().trim();
1837:
1838: xra.add(token.toLowerCase());
1839: }
1840: }
1841:
1842: xmlrpcAccess = xra;
1843:
1844: // if node manager exists, update it
1845: if (nmgr != null) {
1846: nmgr.updateProperties(props);
1847: }
1848:
1849: // update extensions
1850: if (Server.getServer() != null) {
1851: Vector extensions = Server.getServer().getExtensions();
1852:
1853: for (int i = 0; i < extensions.size(); i++) {
1854: HelmaExtension ext = (HelmaExtension) extensions
1855: .get(i);
1856:
1857: try {
1858: ext.applicationUpdated(this );
1859: } catch (ConfigurationException e) {
1860: logEvent("Error updating extension " + ext
1861: + ": " + e);
1862: }
1863: }
1864: }
1865:
1866: logDir = props.getProperty("logdir", "log");
1867: if (System.getProperty("helma.logdir") == null) {
1868: // set up helma.logdir system property in case we're using it
1869: // FIXME: this sets a global System property, should be per-app
1870: File dir = new File(logDir);
1871: System.setProperty("helma.logdir", dir
1872: .getAbsolutePath());
1873: }
1874:
1875: // set log level for event log in case it is a helma.util.Logger
1876: if (eventLog instanceof Logger) {
1877: if (debug && !eventLog.isDebugEnabled())
1878: ((Logger) eventLog).setLogLevel(Logger.DEBUG);
1879: else if (!eventLog.isInfoEnabled())
1880: ((Logger) eventLog).setLogLevel(Logger.INFO);
1881: }
1882:
1883: // set prop read timestamp
1884: lastPropertyRead = props.lastModified();
1885: }
1886: }
1887:
1888: /**
1889: * Get a checksum that mirrors the state of this application in the sense
1890: * that if anything in the applciation changes, the checksum hopefully will
1891: * change, too.
1892: */
1893: public long getChecksum() {
1894: return starttime + typemgr.getLastCodeUpdate()
1895: + props.getChecksum();
1896: }
1897:
1898: /**
1899: * Proxy method to get a property from the applications properties.
1900: */
1901: public String getProperty(String propname) {
1902: return props.getProperty(propname);
1903: }
1904:
1905: /**
1906: * Proxy method to get a property from the applications properties.
1907: */
1908: public String getProperty(String propname, String defvalue) {
1909: return props.getProperty(propname, defvalue);
1910: }
1911:
1912: /**
1913: * Get the application's app properties
1914: *
1915: * @return the properties reflecting the app.properties
1916: */
1917: public ResourceProperties getProperties() {
1918: return props;
1919: }
1920:
1921: /**
1922: * Get the application's db properties
1923: *
1924: * @return the properties reflecting the db.properties
1925: */
1926: public ResourceProperties getDbProperties() {
1927: return dbProps;
1928: }
1929:
1930: /**
1931: * Return the XML-RPC handler name for this app. The contract is to
1932: * always return the same string, even if it has been changed in the properties file
1933: * during runtime, so the app gets unregistered correctly.
1934: */
1935: public String getXmlRpcHandlerName() {
1936: if (xmlrpcHandlerName == null) {
1937: xmlrpcHandlerName = props.getProperty("xmlrpcHandlerName",
1938: this .name);
1939: }
1940:
1941: return xmlrpcHandlerName;
1942: }
1943:
1944: /**
1945: * Return a string representation for this app.
1946: */
1947: public String toString() {
1948: return "[Application " + name + "]";
1949: }
1950:
1951: /**
1952: *
1953: */
1954: public int countThreads() {
1955: return threadgroup.activeCount();
1956: }
1957:
1958: /**
1959: *
1960: */
1961: public int countEvaluators() {
1962: return allThreads.size();
1963: }
1964:
1965: /**
1966: *
1967: */
1968: public int countFreeEvaluators() {
1969: return freeThreads.size();
1970: }
1971:
1972: /**
1973: *
1974: */
1975: public int countActiveEvaluators() {
1976: return allThreads.size() - freeThreads.size();
1977: }
1978:
1979: /**
1980: *
1981: */
1982: public int countMaxActiveEvaluators() {
1983: // return typemgr.countRegisteredRequestEvaluators () -1;
1984: // not available due to framework refactoring
1985: return -1;
1986: }
1987:
1988: /**
1989: *
1990: */
1991: public long getRequestCount() {
1992: return requestCount;
1993: }
1994:
1995: /**
1996: *
1997: */
1998: public long getXmlrpcCount() {
1999: return xmlrpcCount;
2000: }
2001:
2002: /**
2003: *
2004: */
2005: public long getErrorCount() {
2006: return errorCount;
2007: }
2008:
2009: /**
2010: *
2011: *
2012: * @return ...
2013: */
2014: public long getStarttime() {
2015: return starttime;
2016: }
2017:
2018: /**
2019: * Return the name of the character encoding used by this application
2020: *
2021: * @return the character encoding
2022: */
2023: public String getCharset() {
2024: return charset;
2025: }
2026:
2027: /**
2028: * Periodically called to log thread stats for this application
2029: */
2030: public void printThreadStats() {
2031: logEvent("Thread Stats for " + name + ": "
2032: + threadgroup.activeCount() + " active");
2033:
2034: Runtime rt = Runtime.getRuntime();
2035: long free = rt.freeMemory();
2036: long total = rt.totalMemory();
2037:
2038: logEvent("Free memory: " + (free / 1024) + " kB");
2039: logEvent("Total memory: " + (total / 1024) + " kB");
2040: }
2041:
2042: /**
2043: * Check if a method may be invoked via XML-RPC on a prototype.
2044: */
2045: protected void checkXmlRpcAccess(String proto, String method)
2046: throws Exception {
2047: String key = proto + "." + method;
2048:
2049: // XML-RPC access items are case insensitive and stored in lower case
2050: if (!xmlrpcAccess.contains(key.toLowerCase())) {
2051: throw new Exception("Method " + key
2052: + " is not callable via XML-RPC");
2053: }
2054: }
2055:
2056: class CronRunner extends Thread {
2057: RequestEvaluator this Evaluator;
2058: CronJob job;
2059:
2060: public CronRunner(RequestEvaluator this Evaluator, CronJob job) {
2061: this .this Evaluator = this Evaluator;
2062: this .job = job;
2063: }
2064:
2065: public void run() {
2066: try {
2067: this Evaluator.invokeInternal(null, job.getFunction(),
2068: RequestEvaluator.EMPTY_ARGS, job.getTimeout());
2069: } catch (Exception ex) {
2070: logEvent("error running " + job + ": " + ex);
2071: } finally {
2072: releaseEvaluator(this Evaluator);
2073: this Evaluator = null;
2074: activeCronJobs.remove(job.getName());
2075: }
2076: }
2077:
2078: public String toString() {
2079: return "CronRunner[" + job + "]";
2080: }
2081: }
2082: }
|