PageRenderTime 2ms CodeModel.GetById 34ms app.highlight 36ms RepoModel.GetById 11ms app.codeStats 1ms

/src/ServiceStack/ServiceHost/ServiceController.cs

https://github.com/bluetentacle/ServiceStack
C# | 370 lines | 293 code | 74 blank | 3 comment | 29 complexity | c371d0f07123af8b32ff587f7ef554f9 MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using System.Linq.Expressions;
  4using System.Reflection;
  5using System.Text;
  6using ServiceStack.Configuration;
  7using ServiceStack.Logging;
  8using ServiceStack.ServiceModel.Serialization;
  9using ServiceStack.Text;
 10
 11namespace ServiceStack.ServiceHost
 12{
 13	public class ServiceController
 14		: IServiceController
 15	{
 16		private static readonly ILog Log = LogManager.GetLogger(typeof(ServiceController));
 17		private const string ResponseDtoSuffix = "Response";
 18
 19		public ServiceController(Func<IEnumerable<Type>> resolveServicesFn)
 20		{
 21			this.AllOperationTypes = new List<Type>();
 22			this.OperationTypes = new List<Type>();
 23			this.ServiceTypes = new HashSet<Type>();
 24			this.EnableAccessRestrictions = true;
 25			this.routes = new ServiceRoutes();
 26			this.ResolveServicesFn = resolveServicesFn;
 27		}
 28
 29		readonly Dictionary<Type, Func<IRequestContext, object, object>> requestExecMap
 30			= new Dictionary<Type, Func<IRequestContext, object, object>>();
 31
 32		readonly Dictionary<Type, ServiceAttribute> requestServiceAttrs
 33			= new Dictionary<Type, ServiceAttribute>();
 34
 35		private readonly ServiceRoutes routes;
 36
 37		public bool EnableAccessRestrictions { get; set; }
 38
 39		public IList<Type> AllOperationTypes { get; protected set; }
 40
 41		public IList<Type> OperationTypes { get; protected set; }
 42
 43		public HashSet<Type> ServiceTypes { get; protected set; }
 44
 45		public string DefaultOperationsNamespace { get; set; }
 46
 47		public IServiceRoutes Routes { get { return routes; } }
 48
 49		public Func<IEnumerable<Type>> ResolveServicesFn { get; set; }
 50
 51		public void Register<TReq>(Func<IService<TReq>> invoker)
 52		{
 53			var requestType = typeof(TReq);
 54			Func<IRequestContext, object, object> handlerFn = (requestContext, dto) =>
 55			{
 56				var service = invoker();
 57
 58				InjectRequestContext(service, requestContext);
 59
 60				return ServiceExec<TReq>.Execute(
 61					service, (TReq)dto,
 62					requestContext != null ? requestContext.EndpointAttributes : EndpointAttributes.None);
 63			};
 64
 65			requestExecMap.Add(requestType, handlerFn);
 66		}
 67
 68		public void Register(ITypeFactory serviceFactoryFn)
 69		{
 70			foreach (var serviceType in ResolveServicesFn())
 71			{
 72				if (serviceType.IsAbstract || serviceType.ContainsGenericParameters) continue;
 73
 74				foreach (var service in serviceType.GetInterfaces())
 75				{
 76					if (!service.IsGenericType
 77						|| service.GetGenericTypeDefinition() != typeof(IService<>)
 78						) continue;
 79
 80					var requestType = service.GetGenericArguments()[0];
 81
 82					Register(requestType, serviceType, serviceFactoryFn);
 83
 84					RegisterRestPaths(requestType);
 85
 86					this.ServiceTypes.Add(serviceType);
 87
 88					this.AllOperationTypes.Add(requestType);
 89					this.OperationTypes.Add(requestType);
 90
 91					var responseTypeName = requestType.FullName + ResponseDtoSuffix;
 92					var responseType = AssemblyUtils.FindType(responseTypeName);
 93					if (responseType != null)
 94					{
 95						this.AllOperationTypes.Add(responseType);
 96						this.OperationTypes.Add(responseType);
 97					}
 98
 99					Log.DebugFormat("Registering {0} service '{1}' with request '{2}'",
100						(responseType != null ? "SyncReply" : "OneWay"),
101						serviceType.Name, requestType.Name);
102				}
103			}
104		}
105
106		public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>();
107
108		public void RegisterRestPaths(Type requestType)
109		{
110			var attrs = requestType.GetCustomAttributes(typeof(RestServiceAttribute), true);
111			foreach (RestServiceAttribute attr in attrs)
112			{
113				var restPath = new RestPath(requestType, attr.Path, attr.Verbs, attr.DefaultContentType);
114				if (!restPath.IsValid)
115					throw new NotSupportedException(string.Format(
116						"RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.Name));
117
118				RegisterRestPath(restPath);
119			}
120		}
121
122		public void RegisterRestPath(RestPath restPath)
123		{
124			List<RestPath> pathsAtFirstMatch;
125			if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
126			{
127				pathsAtFirstMatch = new List<RestPath>();
128				RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch;
129			}
130			pathsAtFirstMatch.Add(restPath);
131		}
132
133		public void AfterInit()
134		{
135			foreach (var restPath in this.routes.RestPaths)
136			{
137				RegisterRestPath(restPath);
138			}
139		}
140
141		public IRestPath GetRestPathForRequest(string httpMethod, string pathInfo)
142		{
143			var matchUsingPathParts = RestPath.GetPathPartsForMatching(pathInfo);
144
145			List<RestPath> firstMatches;
146
147			var yieldedHashMatches = RestPath.GetFirstMatchHashKeys(matchUsingPathParts);
148			foreach (var potentialHashMatch in yieldedHashMatches)
149			{
150				if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
151
152				foreach (var restPath in firstMatches)
153				{
154					if (restPath.IsMatch(httpMethod, matchUsingPathParts)) return restPath;
155				}
156			}
157
158			var yieldedWildcardMatches = RestPath.GetFirstMatchWildCardHashKeys(matchUsingPathParts);
159			foreach (var potentialHashMatch in yieldedWildcardMatches)
160			{
161				if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
162
163				foreach (var restPath in firstMatches)
164				{
165					if (restPath.IsMatch(httpMethod, matchUsingPathParts)) return restPath;
166				}
167			}
168
169			return null;
170		}
171
172		internal class TypeFactoryWrapper : ITypeFactory
173		{
174			private readonly Func<Type, object> typeCreator;
175
176			public TypeFactoryWrapper(Func<Type, object> typeCreator)
177			{
178				this.typeCreator = typeCreator;
179			}
180
181			public object CreateInstance(Type type)
182			{
183				return typeCreator(type);
184			}
185		}
186
187		public void Register(Type requestType, Type serviceType)
188		{
189			var handlerFactoryFn = Expression.Lambda<Func<Type, object>>
190				(
191					Expression.New(serviceType),
192					Expression.Parameter(typeof(Type), "serviceType")
193				).Compile();
194
195			Register(requestType, serviceType, new TypeFactoryWrapper(handlerFactoryFn));
196		}
197
198		public void Register(Type requestType, Type serviceType, Func<Type, object> handlerFactoryFn)
199		{
200			Register(requestType, serviceType, new TypeFactoryWrapper(handlerFactoryFn));
201		}
202
203		public void Register(Type requestType, Type serviceType, ITypeFactory serviceFactoryFn)
204		{
205			var typeFactoryFn = CallServiceExecuteGeneric(requestType, serviceType);
206
207			Func<IRequestContext, object, object> handlerFn = (requestContext, dto) =>
208			{
209				var service = serviceFactoryFn.CreateInstance(serviceType);
210                using (service as IDisposable) // using is happy if this expression evals to null
211                {
212                    InjectRequestContext(service, requestContext);
213
214                    var endpointAttrs = requestContext != null
215                        ? requestContext.EndpointAttributes
216                        : EndpointAttributes.None;
217
218                    try
219                    {
220                        //Executes the service and returns the result
221                        return typeFactoryFn(dto, service, endpointAttrs);
222                    }
223                    catch (TargetInvocationException tex)
224                    {
225                        //Mono invokes using reflection
226                        throw tex.InnerException ?? tex;
227                    }
228                }
229			};
230
231			try
232			{
233				requestExecMap.Add(requestType, handlerFn);
234			}
235			catch (ArgumentException)
236			{
237				throw new AmbiguousMatchException(
238					string.Format("Could not register the service '{0}' as another service with the definition of type 'IService<{1}>' already exists.",
239					serviceType.FullName, requestType.Name));
240			}
241
242			var serviceAttrs = requestType.GetCustomAttributes(typeof(ServiceAttribute), false);
243			if (serviceAttrs.Length > 0)
244			{
245				requestServiceAttrs.Add(requestType, (ServiceAttribute)serviceAttrs[0]);
246			}
247		}
248
249		private static void InjectRequestContext(object service, IRequestContext requestContext)
250		{
251			if (requestContext == null) return;
252
253			var serviceRequiresContext = service as IRequiresRequestContext;
254			if (serviceRequiresContext != null)
255			{
256				serviceRequiresContext.RequestContext = requestContext;
257			}
258		}
259
260		private static Func<object, object, EndpointAttributes, object> CallServiceExecuteGeneric(
261			Type requestType, Type serviceType)
262		{
263			try
264			{
265				var requestDtoParam = Expression.Parameter(typeof(object), "requestDto");
266				var requestDtoStrong = Expression.Convert(requestDtoParam, requestType);
267
268				var serviceParam = Expression.Parameter(typeof(object), "serviceObj");
269				var serviceStrong = Expression.Convert(serviceParam, serviceType);
270
271				var attrsParam = Expression.Parameter(typeof(EndpointAttributes), "attrs");
272
273				var mi = ServiceExec.GetExecMethodInfo(serviceType, requestType);
274
275				Expression callExecute = Expression.Call(
276					mi, new Expression[] { serviceStrong, requestDtoStrong, attrsParam });
277
278				var executeFunc = Expression.Lambda<Func<object, object, EndpointAttributes, object>>
279					(callExecute, requestDtoParam, serviceParam, attrsParam).Compile();
280
281				return executeFunc;
282
283			}
284			catch (Exception)
285			{
286				//problems with MONO, using reflection for temp fix
287				return delegate(object request, object service, EndpointAttributes attrs)
288				{
289					var mi = ServiceExec.GetExecMethodInfo(serviceType, requestType);
290					return mi.Invoke(null, new[] { service, request, attrs });
291				};
292			}
293		}
294
295		public object Execute(object dto)
296		{
297			return Execute(dto, null);
298		}
299
300		public object Execute(object request, IRequestContext requestContext)
301		{
302			var requestType = request.GetType();
303
304			if (EnableAccessRestrictions)
305			{
306				AssertServiceRestrictions(requestType,
307					requestContext != null ? requestContext.EndpointAttributes : EndpointAttributes.None);
308			}
309
310			var handlerFn = GetService(requestType);
311			return handlerFn(requestContext, request);
312		}
313
314		public Func<IRequestContext, object, object> GetService(Type requestType)
315		{
316			Func<IRequestContext, object, object> handlerFn;
317			if (!requestExecMap.TryGetValue(requestType, out handlerFn))
318			{
319				throw new NotImplementedException(
320						string.Format("Unable to resolve service '{0}'", requestType.Name));
321			}
322
323			return handlerFn;
324		}
325
326		public object ExecuteText(string requestXml, Type requestType, IRequestContext requestContext)
327		{
328			var request = DataContractDeserializer.Instance.Parse(requestXml, requestType);
329			var response = Execute(request, requestContext);
330			var responseXml = DataContractSerializer.Instance.Parse(response);
331			return responseXml;
332		}
333
334		public void AssertServiceRestrictions(Type requestType, EndpointAttributes actualAttributes)
335		{
336			ServiceAttribute serviceAttr;
337			var hasNoAccessRestrictions = !requestServiceAttrs.TryGetValue(requestType, out serviceAttr)
338				|| serviceAttr.HasNoAccessRestrictions;
339
340			if (hasNoAccessRestrictions)
341			{
342				return;
343			}
344
345			var failedScenarios = new StringBuilder();
346			foreach (var requiredScenario in serviceAttr.RestrictAccessToScenarios)
347			{
348				var allServiceRestrictionsMet = (requiredScenario & actualAttributes) == actualAttributes;
349				if (allServiceRestrictionsMet)
350				{
351					return;
352				}
353
354				var passed = requiredScenario & actualAttributes;
355				var failed = requiredScenario & ~(passed);
356
357				failedScenarios.AppendFormat("\n -[{0}]", failed);
358			}
359
360			var internalDebugMsg = (EndpointAttributes.InternalNetworkAccess & actualAttributes) != 0
361				? "\n Unauthorized call was made from: " + actualAttributes
362				: "";
363
364			throw new UnauthorizedAccessException(
365				string.Format("Could not execute service '{0}', The following restrictions were not met: '{1}'" + internalDebugMsg,
366					requestType.Name, failedScenarios));
367		}
368	}
369
370}