protected virtual IDekiScriptInvocationTarget FindTarget(XUri uri) {
     string last_segment = uri.LastSegment ?? string.Empty;
     if(last_segment.EndsWithInvariant(".rpc")) {
         string methodName = last_segment.Substring(0, last_segment.Length - 4);
         return new DekiScriptXmlRpcInvocationTarget(uri.WithoutLastSegment(), methodName);
     }
     if(last_segment.EndsWithInvariant(".jsp")) {
         return new DekiScriptHttpGetInvocationTarget(uri);
     }
     return new DekiScriptRemoteInvocationTarget(uri);
 }
        private void LoadScript() {
            _manifestPath = null;
            _resourcesPath = null;
            _manifestUri = null;
            _resourcesUri = null;
            _manifest = null;

            // read manifest
            _manifestPath = Config["manifest"].AsText;
            if(string.IsNullOrEmpty(_manifestPath)) {
                throw new ArgumentNullException("manifest");
            }
            _manifestUri = XUri.TryParse(_manifestPath);
            if(_manifestUri != null) {
                _manifestPath = null;
                _manifest = Plug.New(_manifestUri).Get().ToDocument();
                _resourcesUri = Config["resources"].AsUri ?? _manifestUri.WithoutLastSegment();
            } else {
                _manifest = XDocFactory.LoadFrom(_manifestPath, MimeType.XML);
                _resourcesPath = Config["resources"].AsText ?? Path.GetDirectoryName(_manifestPath);
            }
            if(!_manifest.HasName("extension")) {
                throw new ArgumentException("invalid extension manifest");
            }

            // initilize runtime
            _runtime = new DekiScriptRuntime();

            // read manifest settings
            _title = _manifest["title"].AsText;
            _label = _manifest["label"].AsText;
            _copyright = _manifest["copyright"].AsText;
            _description = _manifest["description"].AsText;
            _help = _manifest["uri.help"].AsText;
            _logo = _manifest["uri.logo"].AsText;
            _namespace = _manifest["namespace"].AsText;

            // initialize evaluation environment
            _commonEnv = _runtime.CreateEnv();

            // read functions
            _functions = new Dictionary<XUri, DekiScriptInvocationTargetDescriptor>();
            foreach(var function in _manifest["function"]) {
                var descriptor = ConvertFunction(function);
                if(descriptor != null) {
                    var uri = Self.At(descriptor.SystemName);
                    DekiScriptInvocationTargetDescriptor old;
                    if(_functions.TryGetValue(uri, out old)) {
                        _log.WarnFormat("duplicate function name {0} in script {1}", descriptor.Name, _manifestUri);
                    }
                    _functions[uri] = descriptor;
                }
            }
            _runtime.RegisterExtensionFunctions(_functions);

            // add extension functions to env
            foreach(var function in _functions) {
                _commonEnv.Vars.AddNativeValueAt(function.Value.Name.ToLowerInvariant(), function.Key);
            }

            // add configuration settings
            DreamContext context = DreamContext.Current;
            DekiScriptMap scriptConfig = new DekiScriptMap();
            foreach(KeyValuePair<string, string> entry in Config.ToKeyValuePairs()) {
                XUri local;
                if(XUri.TryParse(entry.Value, out local)) {
                    local = context.AsPublicUri(local);
                    scriptConfig.AddAt(entry.Key.Split('/'), DekiScriptExpression.Constant(local.ToString()));
                } else {
                    scriptConfig.AddAt(entry.Key.Split('/'), DekiScriptExpression.Constant(entry.Value));
                }
            }
            _commonEnv.Vars.Add("config", scriptConfig);
        }
        //--- Constructor ---
        public DekiInstance(DekiWikiService deki, string id, XDoc instanceConfig) {
            if(deki == null) {
                throw new ArgumentNullException("deki");
            }
            if(string.IsNullOrEmpty(id)) {
                throw new ArgumentNullException("id");
            }

            this.Id = id;
            this.TimerFactory = TaskTimerFactory.Create(this);
            this.Cache = new DreamCache(TimerFactory);
            var cacheFactory = new InMemoryKeyValueCacheFactory(TimerFactory);
            var searchSerializer = new SearchSerializer();
            cacheFactory.SetSerializer<SearchResult>(searchSerializer);
            cacheFactory.SetSerializer<SearchResultDetail>(searchSerializer);
            this.SearchCache = cacheFactory.Create();
            this.Config = instanceConfig;
            this.Log = LogManager.GetLogger(deki.GetType().Name + "_" + id);
            _deki = deki;
            _status = DekiInstanceStatus.CREATED;
            foreach(XDoc hostDoc in Config["host"]) {
                string host = hostDoc.Contents;
                if(!StringUtil.EqualsInvariantIgnoreCase(host, "*")) {
                    string port = hostDoc["@port"].AsText;
                    string scheme = hostDoc["@https"].AsBool.GetValueOrDefault() ? "https://" : "http://";
                    string uri = scheme + host + (string.IsNullOrEmpty(port) ? "" : ":" + port);
                    _canonicalUri = new XUri(uri);
                    _log.DebugFormat("divined canonical use from hosts as {0}", _canonicalUri);
                    break;
                }
            }
            if(_canonicalUri == null) {

                // Note (arnec): this is a best guess fallback. It will only work in these scenarios:
                // a) The host was set up with a uri.public that has ends in @api and with the @api points to the site uri, or
                // b) The api lives on the same machine as the site, so that deriving uri.public for the host from the machine
                // IP happens to point to the same machine
                // Either way it relies on the hard-coded assumption that the api is accessible via {site}/@api
                _canonicalUri = DreamContext.Current.ServerUri;
                if(_canonicalUri.LastSegment.EqualsInvariantIgnoreCase("@api")) {
                    _canonicalUri = _canonicalUri.WithoutLastSegment();
                }
                _log.DebugFormat("using server uri as canonical uri: {0}", _canonicalUri);
            } else {

                // Note (arnec): Propagating a much hard-coded assumption, i.e. that the Api for any Deki instance can be accessed
                // at the instances' canonical uri plus @api

                // register the api uri with the dream host so that requests originating from within Dream are guaranteed to be locally routed
                _deki.Env.At("status", "aliases").Post(new XDoc("aliases").Elem("uri.alias", _canonicalUri.At("@api")), new Result<DreamMessage>());
            }
        }