Beispiel #1
0
        /// <summary>
        /// Creates a new stage instance.
        /// </summary>
        /// <param name="service">Service instance to which the stage belongs to.</param>
        /// <param name="method">Method definintion for stage handler.</param>
        /// <param name="access">Stage access level.</param>
        public DreamFeatureStage(IDreamService service, MethodInfo method, DreamAccess access)
        {
            if (service == null)
            {
                throw new ArgumentNullException("service");
            }
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }
            this.Name   = service.GetType().FullName + "!" + method.Name;
            this.Access = access;
            _method     = method;
            _service    = service;

            // determine what kind of method we were given
            var parameters = method.GetParameters();

            if ((method.ReturnType == typeof(Yield)) && (parameters.Length == 3) && (parameters[0].ParameterType == typeof(DreamContext)) && (parameters[1].ParameterType == typeof(DreamMessage)) && (parameters[2].ParameterType == typeof(Result <DreamMessage>)))
            {
                // classical coroutine feature handler
                _handler = (CoroutineHandler <DreamContext, DreamMessage, Result <DreamMessage> >)Delegate.CreateDelegate(typeof(CoroutineHandler <DreamContext, DreamMessage, Result <DreamMessage> >), service, method);
            }
            else
            {
                // validate method return type
                if (method.ReturnType != typeof(Yield) && method.ReturnType != typeof(DreamMessage) && method.ReturnType != typeof(XDoc))
                {
                    throw new InvalidCastException(string.Format("feature handler '{0}' has return type {1}, but should be either DreamMessage or IEnumerator<IYield>", method.Name, method.ReturnType));
                }

                // create an execution plan for fetching the necessary parameters to invoke the method
                _plan = new List <DreamFeatureAdapter>();
                foreach (var param in parameters)
                {
                    // check name-based parameters
                    if (param.Name.EqualsInvariant("verb"))
                    {
                        Assert(method, param, typeof(string));
                        _plan.Add(new DreamFeatureAdapter(param.Name, GetContextVerb));
                    }
                    else if (param.Name.EqualsInvariant("path"))
                    {
                        Assert(method, param, typeof(string[]), typeof(string));
                        if (param.ParameterType == typeof(string))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpath));
                        }
                        else
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpathSegments));
                        }
                    }
                    else if (param.Name.EqualsInvariant("uri"))
                    {
                        Assert(method, param, typeof(XUri));
                        _plan.Add(new DreamFeatureAdapter(param.Name, GetContextUri));
                    }
                    else if (param.Name.EqualsInvariant("body"))
                    {
                        Assert(method, param, typeof(XDoc), typeof(string), typeof(Stream), typeof(byte[]));

                        // check which body type is requested
                        if (param.ParameterType == typeof(XDoc))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsDocument));
                        }
                        else if (param.ParameterType == typeof(string))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsText));
                        }
                        else if (param.ParameterType == typeof(Stream))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsStream));
                        }
                        else if (param.ParameterType == typeof(byte[]))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsBytes));
                        }
                        else
                        {
                            throw new ShouldNeverHappenException();
                        }
                    }
                    else
                    {
                        // check type-based parameters
                        if (param.ParameterType == typeof(DreamContext))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContext));
                        }
                        else if (param.ParameterType == typeof(DreamMessage))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequest));
                        }
                        else if (param.ParameterType == typeof(Result <DreamMessage>))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetMessageResponse));
                        }
                        else if (param.ParameterType == typeof(Result <XDoc>))
                        {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetDocumentResponse));
                        }
                        else if (param.ParameterType == typeof(DreamCookie))
                        {
                            _plan.Add(MakeRequestCookieGetter(param.Name));
                        }
                        else if (param.ParameterType == typeof(string))
                        {
                            _plan.Add(MakeContextParamGetter(param.Name));
                        }
                        else if (param.ParameterType == typeof(string[]))
                        {
                            _plan.Add(MakeContextParamListGetter(param.Name));
                        }
                        else
                        {
                            _plan.Add(MakeConvertingContextParamGetter(param.Name, param.ParameterType));
                        }
                    }
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Creates a new stage instance.
        /// </summary>
        /// <param name="service">Service instance to which the stage belongs to.</param>
        /// <param name="method">Method definintion for stage handler.</param>
        /// <param name="access">Stage access level.</param>
        public DreamFeatureStage(IDreamService service, MethodInfo method, DreamAccess access)
        {
            if(service == null) {
                throw new ArgumentNullException("service");
            }
            if(method == null) {
                throw new ArgumentNullException("method");
            }
            this.Name = service.GetType().FullName + "!" + method.Name;
            this.Access = access;
            _method = method;
            _service = service;

            // determine what kind of method we were given
            var parameters = method.GetParameters();
            if((method.ReturnType == typeof(Yield)) && (parameters.Length == 3) && (parameters[0].ParameterType == typeof(DreamContext)) && (parameters[1].ParameterType == typeof(DreamMessage)) && (parameters[2].ParameterType == typeof(Result<DreamMessage>))) {

                // classical coroutine feature handler
                _handler = (CoroutineHandler<DreamContext, DreamMessage, Result<DreamMessage>>)Delegate.CreateDelegate(typeof(CoroutineHandler<DreamContext, DreamMessage, Result<DreamMessage>>), service, method);
            } else {

                // TODO (arnec): Eventually DreamMessage should have a DreamMessage<T> with custom serializers allowing arbitrary return types

                // validate method return type
                if(method.ReturnType != typeof(Yield) && method.ReturnType != typeof(DreamMessage) && method.ReturnType != typeof(XDoc)) {
                    throw new InvalidCastException(string.Format("feature handler '{0}' has return type {1}, but should be either DreamMessage or IEnumerator<IYield>", method.Name, method.ReturnType));
                }

                // create an execution plan for fetching the necessary parameters to invoke the method
                _plan = new List<DreamFeatureAdapter>();
                foreach(var param in method.GetParameters()) {
                    var attributes = param.GetCustomAttributes(false);
                    QueryAttribute queryParam = (QueryAttribute)attributes.FirstOrDefault(i => i is QueryAttribute);
                    PathAttribute pathParam = (PathAttribute)attributes.FirstOrDefault(i => i is PathAttribute);
                    HeaderAttribute header = (HeaderAttribute)attributes.FirstOrDefault(i => i is HeaderAttribute);
                    CookieAttribute cookie = (CookieAttribute)attributes.FirstOrDefault(i => i is CookieAttribute);

                    // check attribute-based parameters
                    if(queryParam != null) {

                        // check if a single or a list of query parameters are requested
                        if(param.ParameterType == typeof(string)) {
                            _plan.Add(MakeContextParamGetter(queryParam.Name ?? param.Name));
                        } else if(param.ParameterType == typeof(string[])) {
                            _plan.Add(MakeContextParamListGetter(queryParam.Name ?? param.Name));
                        } else {
                            _plan.Add(MakeConvertingContextParamGetter(queryParam.Name ?? param.Name, param.ParameterType));
                        }
                    } else if(pathParam != null) {
                        if(param.ParameterType == typeof(string)) {
                            _plan.Add(MakeContextParamGetter(pathParam.Name ?? param.Name));
                        } else {
                            _plan.Add(MakeConvertingContextParamGetter(pathParam.Name ?? param.Name, param.ParameterType));
                        }
                    } else if(cookie != null) {
                        Assert(method, param, typeof(string), typeof(DreamCookie));

                        // check which cookie type is requested
                        if(param.ParameterType == typeof(string)) {
                            _plan.Add(MakeRequestCookieValueGetter(cookie.Name ?? param.Name));
                        } else if(param.ParameterType == typeof(DreamCookie)) {
                            _plan.Add(MakeRequestCookieGetter(cookie.Name ?? param.Name));
                        } else {
                            throw new ShouldNeverHappenException();
                        }
                    } else if(header != null) {
                        Assert(method, param, typeof(string));
                        _plan.Add(MakeRequestHeaderGetter(header.Name ?? param.Name));
                    } else {

                        // check name-based parameters
                        if(param.Name.EqualsInvariant("verb")) {
                            Assert(method, param, typeof(string));
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextVerb));
                        } else if(param.Name.EqualsInvariant("path")) {

                            Assert(method, param, typeof(string[]), typeof(string));
                            if(param.ParameterType == typeof(string)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpath));
                            } else {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpathSegments));
                            }
                        } else if(param.Name.EqualsInvariant("uri")) {
                            Assert(method, param, typeof(XUri));
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextUri));
                        } else if(param.Name.EqualsInvariant("body")) {
                            Assert(method, param, typeof(XDoc), typeof(string), typeof(Stream), typeof(byte[]));

                            // check which body type is requested
                            if(param.ParameterType == typeof(XDoc)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsDocument));
                            } else if(param.ParameterType == typeof(string)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsText));
                            } else if(param.ParameterType == typeof(Stream)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsStream));
                            } else if(param.ParameterType == typeof(byte[])) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsBytes));
                            } else {
                                throw new ShouldNeverHappenException();
                            }
                        } else {

                            // check type-based parameters
                            if(param.ParameterType == typeof(DreamContext)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetContext));
                            } else if(param.ParameterType == typeof(DreamMessage)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequest));
                            } else if(param.ParameterType == typeof(Result<DreamMessage>)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetMessageResponse));
                            } else if(param.ParameterType == typeof(Result<XDoc>)) {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetDocumentResponse));
                            } else if(param.ParameterType == typeof(DreamCookie)) {
                                _plan.Add(MakeRequestCookieGetter(param.Name));
                            } else if(param.ParameterType == typeof(string)) {
                                _plan.Add(MakeContextParamGetter(param.Name));
                            } else if(param.ParameterType == typeof(string[])) {
                                _plan.Add(MakeContextParamListGetter(param.Name));
                            } else {
                                _plan.Add(MakeConvertingContextParamGetter(param.Name, param.ParameterType));
                            }
                        }
                    }
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Creates a new stage instance.
        /// </summary>
        /// <param name="service">Service instance to which the stage belongs to.</param>
        /// <param name="method">Method definintion for stage handler.</param>
        /// <param name="access">Stage access level.</param>
        public DreamFeatureStage(IDreamService service, MethodInfo method, DreamAccess access)
        {
            if(service == null) {
                throw new ArgumentNullException("service");
            }
            if(method == null) {
                throw new ArgumentNullException("method");
            }
            this.Name = service.GetType().FullName + "!" + method.Name;
            this.Access = access;
            _method = method;
            _service = service;

            // determine what kind of method we were given
            var parameters = method.GetParameters();
            if((method.ReturnType == typeof(Yield)) && (parameters.Length == 3) && (parameters[0].ParameterType == typeof(DreamContext)) && (parameters[1].ParameterType == typeof(DreamMessage)) && (parameters[2].ParameterType == typeof(Result<DreamMessage>))) {

                // classical coroutine feature handler
                _handler = (CoroutineHandler<DreamContext, DreamMessage, Result<DreamMessage>>)Delegate.CreateDelegate(typeof(CoroutineHandler<DreamContext, DreamMessage, Result<DreamMessage>>), service, method);
            } else {

                // validate method return type
                if(method.ReturnType != typeof(Yield) && method.ReturnType != typeof(DreamMessage) && method.ReturnType != typeof(XDoc)) {
                    throw new InvalidCastException(string.Format("feature handler '{0}' has return type {1}, but should be either DreamMessage or IEnumerator<IYield>", method.Name, method.ReturnType));
                }

                // create an execution plan for fetching the necessary parameters to invoke the method
                _plan = new List<DreamFeatureAdapter>();
                foreach(var param in parameters) {

                    // check name-based parameters
                    if(param.Name.EqualsInvariant("verb")) {
                        Assert(method, param, typeof(string));
                        _plan.Add(new DreamFeatureAdapter(param.Name, GetContextVerb));
                    } else if(param.Name.EqualsInvariant("path")) {
                        Assert(method, param, typeof(string[]), typeof(string));
                        if(param.ParameterType == typeof(string)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpath));
                        } else {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpathSegments));
                        }
                    } else if(param.Name.EqualsInvariant("uri")) {
                        Assert(method, param, typeof(XUri));
                        _plan.Add(new DreamFeatureAdapter(param.Name, GetContextUri));
                    } else if(param.Name.EqualsInvariant("body")) {
                        Assert(method, param, typeof(XDoc), typeof(string), typeof(Stream), typeof(byte[]));

                        // check which body type is requested
                        if(param.ParameterType == typeof(XDoc)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsDocument));
                        } else if(param.ParameterType == typeof(string)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsText));
                        } else if(param.ParameterType == typeof(Stream)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsStream));
                        } else if(param.ParameterType == typeof(byte[])) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsBytes));
                        } else {
                            throw new ShouldNeverHappenException();
                        }
                    } else {

                        // check type-based parameters
                        if(param.ParameterType == typeof(DreamContext)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContext));
                        } else if(param.ParameterType == typeof(DreamMessage)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetRequest));
                        } else if(param.ParameterType == typeof(Result<DreamMessage>)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetMessageResponse));
                        } else if(param.ParameterType == typeof(Result<XDoc>)) {
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetDocumentResponse));
                        } else if(param.ParameterType == typeof(DreamCookie)) {
                            _plan.Add(MakeRequestCookieGetter(param.Name));
                        } else if(param.ParameterType == typeof(string)) {
                            _plan.Add(MakeContextParamGetter(param.Name));
                        } else if(param.ParameterType == typeof(string[])) {
                            _plan.Add(MakeContextParamListGetter(param.Name));
                        } else {
                            _plan.Add(MakeConvertingContextParamGetter(param.Name, param.ParameterType));
                        }
                    }
                }
            }
        }
Beispiel #4
0
        private Yield StartService(IDreamService service, XDoc blueprint, string path, XDoc config, Result<XDoc> result)
        {
            Result<DreamMessage> r;
            path = path.ToLowerInvariant();
            if(_services.ContainsKey(path)) {
                string message = string.Format("conflicting uri: {0}", path);
                _log.Warn(message);
                throw new ArgumentException(message);
            }

            // TODO (steveb): validate all fields in the blueprint (presence & validity)

            // add fresh information
            Type type = service.GetType();
            XUri sid = config["sid"].AsUri ?? blueprint["sid"].AsUri;
            XUri owner = config["uri.owner"].AsUri;

            // create directory of service features
            DreamFeatureDirectory features = CreateServiceFeatureDirectory(service, blueprint, config);
            XUri uri = config["uri.self"].AsUri;

            // now that we have the uri, we can add the storage information (if we already started the host storage service!)
            Plug serviceStorage = null;
            if(_storage != null) {
                var encodedPath = EncodedServicePath(uri);

                // check if private storage is requested
                if(!blueprint["setup/private-storage"].IsEmpty) {

                    // set storage configuration
                    // TODO (arnec): currently new private services are rooted inside shared private service, which means they
                    // could be accessed by the shared private users
                    if(_storageType.EqualsInvariant("s3")) {

                        // Note (arnec): For S3 we can't use Path.Combine, since it might use a different separator from '/'
                        var servicePath = new StringBuilder(_storagePath);
                        if(!servicePath[servicePath.Length - 1].Equals('/')) {
                            servicePath.Append("/");
                        }
                        if(encodedPath[0].Equals('/')) {
                            servicePath.Append(encodedPath.Substring(1));
                        } else {
                            servicePath.Append(encodedPath);
                        }
                        yield return CreateService(
                            "private-storage/" + encodedPath,
                            "sid://mindtouch.com/2010/10/dream/s3.storage.private",
                            new XDoc("config")
                                .Elem("folder", servicePath.ToString())
                                .AddNodes(_storageConfig),
                            new Result<Plug>()).Set(v => serviceStorage = v);

                    } else {
                        var servicePath = Path.Combine(_storagePath, encodedPath);
                        yield return CreateService(
                            "private-storage/" + encodedPath,
                            "sid://mindtouch.com/2007/07/dream/storage.private",
                            new XDoc("config").Elem("folder", servicePath),
                            new Result<Plug>()).Set(v => serviceStorage = v);
                    }
                    config.Elem("uri.storage", serviceStorage.Uri);
                    var cookies = Cookies;
                    lock(cookies) {
                        foreach(var cookie in cookies.Fetch(serviceStorage.Uri)) {
                            config.Add(cookie.AsSetCookieDocument);
                        }
                    }
                } else {
                    // use central private storage
                    config.Elem("uri.storage", _storage.Uri.At(encodedPath));

                    // get central storage's internal access key
                    DreamCookieJar cookies = Cookies;
                    lock(cookies) {
                        foreach(DreamCookie cookie in cookies.Fetch(_storage.Uri)) {
                            config.Add(cookie.AsSetCookieDocument);
                        }
                    }

                }
            }

            // check if we're bootstrapping (i.e. starting ourself!)
            if(Self != null) {

                // add 'internal' access key
                config.Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument);
            }

            // initialize service
            try {
                service.Initialize(this, blueprint);
            } catch {
                string message = string.Format("StartService: service initialization failed ({0} : {1})", path, sid);
                _log.Warn(message);
                throw new DreamBadRequestException(message);
            }

            // activate features
            lock(_features) {
                _features.Add(uri.Segments, 0, features);
            }

            // start service
            yield return r = Plug.New(uri).At("@config").Put(config, new Result<DreamMessage>(TimeSpan.MaxValue));
            XDoc resultDoc;
            if(r.Value.IsSuccessful) {

                // report service as started
                lock(_services) {

                    // TODO (steveb): this operation may fail if two services attempt to register at the same uri; in which case the service should be stopped.
                    _services.Add(uri.Path.ToLowerInvariant(), new ServiceEntry(service, uri, owner, sid, blueprint));
                }
                resultDoc = r.Value.ToDocument();
            } else {
                StopService(uri);
                if(serviceStorage != null) {
                    StopService(serviceStorage);
                }

                // deactivate features
                lock(_features) {
                    _features.Remove(uri);
                }
                _log.ErrorExceptionMethodCall(null, "StartService", (sid != null) ? (object)sid : (object)type.FullName);
                string message = string.Format("service initialization failed: {0} ({1})", uri, sid);
                _log.Warn(message);
                throw new DreamAbortException(r.Value);
            }
            _log.DebugFormat("StartService: service started: {0} ({1})", uri, sid);
            result.Return(resultDoc);
        }
Beispiel #5
0
        private DreamFeatureDirectory CreateServiceFeatureDirectory(IDreamService service, XDoc blueprint, XDoc config)
        {
            Type type = service.GetType();
            string path = config["path"].Contents.ToLowerInvariant();

            // add transport information
            XUri serviceUri = LocalMachineUri.AtAbsolutePath(path);
            config.Root.Elem("uri.self", serviceUri.ToString());

            // compile list of active service features, combined by suffix
            int serviceUriSegmentsLength = serviceUri.Segments.Length;
            DreamFeatureDirectory directory = new DreamFeatureDirectory();
            var methodInfos = GetMethodInfos(type);
            foreach(XDoc featureBlueprint in blueprint["features/feature"]) {
                string methodName = featureBlueprint["method"].Contents;
                string pattern = featureBlueprint["pattern"].AsText;

                // TODO (steveb): we should be a little more discerning here as this might trigger false positives
                bool atConfig = pattern.ContainsInvariantIgnoreCase("@config");

                // locate method
                var methods = methodInfos[methodName];
                if(methods.Count() > 1) {
                    var found = string.Join(", ", methods.Select(m => m.DeclaringType.FullName + "!" + m.Name + "(" + string.Join(", ", m.GetParameters().Select(p => p.ParameterType.Name + " " + p.Name).ToArray()) + ")").ToArray());
                    throw new MissingMethodException(string.Format("found multiple definitions for {0}: {1}", methodName, found));
                }
                if(methods.None()) {
                    throw new MissingMethodException(string.Format("could not find {0} in class {1}", methodName, type.FullName));
                }
                MethodInfo method = methods.First();

                // determine access level
                DreamAccess access;
                switch(featureBlueprint["access"].AsText) {
                case null:
                case "public":
                    access = DreamAccess.Public;
                    break;
                case "internal":
                    access = DreamAccess.Internal;
                    break;
                case "private":
                    access = DreamAccess.Private;
                    break;
                default:
                    throw new NotSupportedException(string.Format("access level is not supported ({0})", methodName));
                }

                // parse pattern string
                string[] parts = pattern.Split(new[] { ':' }, 2);
                string verb = parts[0].Trim();
                string signature = parts[1].Trim();
                if(signature.Length == 0) {
                    signature = string.Empty;
                }

                // add feature prologues
                List<DreamFeatureStage> stages = new List<DreamFeatureStage>();
                stages.AddRange(_defaultPrologues);
                if(!atConfig) {
                    DreamFeatureStage[] custom = service.Prologues;
                    if(!ArrayUtil.IsNullOrEmpty(custom)) {
                        stages.AddRange(custom);
                    }
                }

                // add feature handler
                int mainStageIndex = stages.Count;
                stages.Add(new DreamFeatureStage(service, method, access));

                // add feature epilogues
                if(!atConfig) {
                    DreamFeatureStage[] custom = service.Epilogues;
                    if(!ArrayUtil.IsNullOrEmpty(custom)) {
                        stages.AddRange(custom);
                    }
                }
                stages.AddRange(_defaultEpilogues);

                // create dream feature and add to service directory
                var paramAttributes = method.GetCustomAttributes(typeof(DreamFeatureParamAttribute), false).Cast<DreamFeatureParamAttribute>().ToArray();
                DreamFeature feature = new DreamFeature(service, serviceUri, mainStageIndex, stages.ToArray(), verb, signature, paramAttributes);
                directory.Add(feature.PathSegments, serviceUriSegmentsLength, feature);
            }
            return directory;
        }
        /// <summary>
        /// Creates a new stage instance.
        /// </summary>
        /// <param name="service">Service instance to which the stage belongs to.</param>
        /// <param name="method">Method definintion for stage handler.</param>
        /// <param name="access">Stage access level.</param>
        public DreamFeatureStage(IDreamService service, MethodInfo method, DreamAccess access)
        {
            if (service == null)
            {
                throw new ArgumentNullException("service");
            }
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }
            this.Name   = service.GetType().FullName + "!" + method.Name;
            this.Access = access;
            _method     = method;
            _service    = service;

            // determine what kind of method we were given
            var parameters = method.GetParameters();

            if ((method.ReturnType == typeof(Yield)) && (parameters.Length == 3) && (parameters[0].ParameterType == typeof(DreamContext)) && (parameters[1].ParameterType == typeof(DreamMessage)) && (parameters[2].ParameterType == typeof(Result <DreamMessage>)))
            {
                // classical coroutine feature handler
                _handler = (CoroutineHandler <DreamContext, DreamMessage, Result <DreamMessage> >)Delegate.CreateDelegate(typeof(CoroutineHandler <DreamContext, DreamMessage, Result <DreamMessage> >), service, method);
            }
            else
            {
                // TODO (arnec): Eventually DreamMessage should have a DreamMessage<T> with custom serializers allowing arbitrary return types

                // validate method return type
                if (method.ReturnType != typeof(Yield) && method.ReturnType != typeof(DreamMessage) && method.ReturnType != typeof(XDoc))
                {
                    throw new InvalidCastException(string.Format("feature handler '{0}' has return type {1}, but should be either DreamMessage or IEnumerator<IYield>", method.Name, method.ReturnType));
                }

                // create an execution plan for fetching the necessary parameters to invoke the method
                _plan = new List <DreamFeatureAdapter>();
                foreach (var param in method.GetParameters())
                {
                    var             attributes = param.GetCustomAttributes(false);
                    QueryAttribute  queryParam = (QueryAttribute)attributes.FirstOrDefault(i => i is QueryAttribute);
                    PathAttribute   pathParam  = (PathAttribute)attributes.FirstOrDefault(i => i is PathAttribute);
                    HeaderAttribute header     = (HeaderAttribute)attributes.FirstOrDefault(i => i is HeaderAttribute);
                    CookieAttribute cookie     = (CookieAttribute)attributes.FirstOrDefault(i => i is CookieAttribute);

                    // check attribute-based parameters
                    if (queryParam != null)
                    {
                        // check if a single or a list of query parameters are requested
                        if (param.ParameterType == typeof(string))
                        {
                            _plan.Add(MakeContextParamGetter(queryParam.Name ?? param.Name));
                        }
                        else if (param.ParameterType == typeof(string[]))
                        {
                            _plan.Add(MakeContextParamListGetter(queryParam.Name ?? param.Name));
                        }
                        else
                        {
                            _plan.Add(MakeConvertingContextParamGetter(queryParam.Name ?? param.Name, param.ParameterType));
                        }
                    }
                    else if (pathParam != null)
                    {
                        if (param.ParameterType == typeof(string))
                        {
                            _plan.Add(MakeContextParamGetter(pathParam.Name ?? param.Name));
                        }
                        else
                        {
                            _plan.Add(MakeConvertingContextParamGetter(pathParam.Name ?? param.Name, param.ParameterType));
                        }
                    }
                    else if (cookie != null)
                    {
                        Assert(method, param, typeof(string), typeof(DreamCookie));

                        // check which cookie type is requested
                        if (param.ParameterType == typeof(string))
                        {
                            _plan.Add(MakeRequestCookieValueGetter(cookie.Name ?? param.Name));
                        }
                        else if (param.ParameterType == typeof(DreamCookie))
                        {
                            _plan.Add(MakeRequestCookieGetter(cookie.Name ?? param.Name));
                        }
                        else
                        {
                            throw new ShouldNeverHappenException();
                        }
                    }
                    else if (header != null)
                    {
                        Assert(method, param, typeof(string));
                        _plan.Add(MakeRequestHeaderGetter(header.Name ?? param.Name));
                    }
                    else
                    {
                        // check name-based parameters
                        if (param.Name.EqualsInvariant("verb"))
                        {
                            Assert(method, param, typeof(string));
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextVerb));
                        }
                        else if (param.Name.EqualsInvariant("path"))
                        {
                            Assert(method, param, typeof(string[]), typeof(string));
                            if (param.ParameterType == typeof(string))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpath));
                            }
                            else
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetContextFeatureSubpathSegments));
                            }
                        }
                        else if (param.Name.EqualsInvariant("uri"))
                        {
                            Assert(method, param, typeof(XUri));
                            _plan.Add(new DreamFeatureAdapter(param.Name, GetContextUri));
                        }
                        else if (param.Name.EqualsInvariant("body"))
                        {
                            Assert(method, param, typeof(XDoc), typeof(string), typeof(Stream), typeof(byte[]));

                            // check which body type is requested
                            if (param.ParameterType == typeof(XDoc))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsDocument));
                            }
                            else if (param.ParameterType == typeof(string))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsText));
                            }
                            else if (param.ParameterType == typeof(Stream))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsStream));
                            }
                            else if (param.ParameterType == typeof(byte[]))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequestAsBytes));
                            }
                            else
                            {
                                throw new ShouldNeverHappenException();
                            }
                        }
                        else
                        {
                            // check type-based parameters
                            if (param.ParameterType == typeof(DreamContext))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetContext));
                            }
                            else if (param.ParameterType == typeof(DreamMessage))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetRequest));
                            }
                            else if (param.ParameterType == typeof(Result <DreamMessage>))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetMessageResponse));
                            }
                            else if (param.ParameterType == typeof(Result <XDoc>))
                            {
                                _plan.Add(new DreamFeatureAdapter(param.Name, GetDocumentResponse));
                            }
                            else if (param.ParameterType == typeof(DreamCookie))
                            {
                                _plan.Add(MakeRequestCookieGetter(param.Name));
                            }
                            else if (param.ParameterType == typeof(string))
                            {
                                _plan.Add(MakeContextParamGetter(param.Name));
                            }
                            else if (param.ParameterType == typeof(string[]))
                            {
                                _plan.Add(MakeContextParamListGetter(param.Name));
                            }
                            else
                            {
                                _plan.Add(MakeConvertingContextParamGetter(param.Name, param.ParameterType));
                            }
                        }
                    }
                }
            }
        }