github.com/CiscoM31/godata@v1.0.10/service.go (about) 1 package godata 2 3 import ( 4 "context" 5 "net/http" 6 "net/url" 7 "strings" 8 ) 9 10 const ( 11 ODataFieldContext string = "@odata.context" 12 ODataFieldCount string = "@odata.count" 13 ODataFieldValue string = "value" 14 ) 15 16 // The basic interface for a GoData provider. All providers must implement 17 // these functions. 18 type GoDataProvider interface { 19 // Request a single entity from the provider. Should return a response field 20 // that contains the value mapping properties to values for the entity. 21 GetEntity(*GoDataRequest) (*GoDataResponseField, error) 22 // Request a collection of entities from the provider. Should return a 23 // response field that contains the value of a slice of every entity in the 24 // collection filtered by the request query parameters. 25 GetEntityCollection(*GoDataRequest) (*GoDataResponseField, error) 26 // Request the number of entities in a collection, disregarding any filter 27 // query parameters. 28 GetCount(*GoDataRequest) (int, error) 29 // Get the object model representation from the provider. 30 GetMetadata() *GoDataMetadata 31 } 32 33 // A GoDataService will spawn an HTTP listener, which will connect GoData 34 // requests with a backend provider given to it. 35 type GoDataService struct { 36 // The base url for the service. Navigating to this URL will display the 37 // service document. 38 BaseUrl *url.URL 39 // The provider for this service that is serving the data to the OData API. 40 Provider GoDataProvider 41 // Metadata cache taken from the provider. 42 Metadata *GoDataMetadata 43 // A mapping from schema names to schema references 44 SchemaLookup map[string]*GoDataSchema 45 // A bottom-up mapping from entity type names to schema namespaces to 46 // the entity type reference 47 EntityTypeLookup map[string]map[string]*GoDataEntityType 48 // A bottom-up mapping from entity container names to schema namespaces to 49 // the entity container reference 50 EntityContainerLookup map[string]map[string]*GoDataEntityContainer 51 // A bottom-up mapping from entity set names to entity collection names to 52 // schema namespaces to the entity set reference 53 EntitySetLookup map[string]map[string]map[string]*GoDataEntitySet 54 // A lookup for entity properties if an entity type is given, lookup 55 // properties by name 56 PropertyLookup map[*GoDataEntityType]map[string]*GoDataProperty 57 // A lookup for navigational properties if an entity type is given, 58 // lookup navigational properties by name 59 NavigationPropertyLookup map[*GoDataEntityType]map[string]*GoDataNavigationProperty 60 } 61 62 type providerChannelResponse struct { 63 Field *GoDataResponseField 64 Error error 65 } 66 67 // Create a new service from a given provider. This step builds lookups for 68 // all parts of the data model, so constant time lookups can be performed. This 69 // step only happens once when the server starts up, so the overall cost is 70 // minimal. The given url will be treated as the base URL for all service 71 // requests, and used for building context URLs, etc. 72 func BuildService(provider GoDataProvider, serviceUrl string) (*GoDataService, error) { 73 metadata := provider.GetMetadata() 74 75 // build the lookups from the metadata 76 schemaLookup := map[string]*GoDataSchema{} 77 entityLookup := map[string]map[string]*GoDataEntityType{} 78 containerLookup := map[string]map[string]*GoDataEntityContainer{} 79 entitySetLookup := map[string]map[string]map[string]*GoDataEntitySet{} 80 propertyLookup := map[*GoDataEntityType]map[string]*GoDataProperty{} 81 navPropLookup := map[*GoDataEntityType]map[string]*GoDataNavigationProperty{} 82 83 for _, schema := range metadata.DataServices.Schemas { 84 schemaLookup[schema.Namespace] = schema 85 86 for _, entity := range schema.EntityTypes { 87 if _, ok := entityLookup[entity.Name]; !ok { 88 entityLookup[entity.Name] = map[string]*GoDataEntityType{} 89 } 90 if _, ok := propertyLookup[entity]; !ok { 91 propertyLookup[entity] = map[string]*GoDataProperty{} 92 } 93 if _, ok := navPropLookup[entity]; !ok { 94 navPropLookup[entity] = map[string]*GoDataNavigationProperty{} 95 } 96 entityLookup[entity.Name][schema.Namespace] = entity 97 98 for _, prop := range entity.Properties { 99 propertyLookup[entity][prop.Name] = prop 100 } 101 for _, prop := range entity.NavigationProperties { 102 navPropLookup[entity][prop.Name] = prop 103 } 104 } 105 106 for _, container := range schema.EntityContainers { 107 if _, ok := containerLookup[container.Name]; !ok { 108 containerLookup[container.Name] = map[string]*GoDataEntityContainer{} 109 } 110 containerLookup[container.Name][schema.Namespace] = container 111 112 for _, set := range container.EntitySets { 113 if _, ok := entitySetLookup[set.Name]; !ok { 114 entitySetLookup[set.Name] = map[string]map[string]*GoDataEntitySet{} 115 } 116 if _, ok := entitySetLookup[set.Name][container.Name]; !ok { 117 entitySetLookup[set.Name][container.Name] = map[string]*GoDataEntitySet{} 118 } 119 entitySetLookup[set.Name][container.Name][schema.Namespace] = set 120 } 121 } 122 } 123 124 parsedUrl, err := url.Parse(serviceUrl) 125 126 if err != nil { 127 return nil, err 128 } 129 130 return &GoDataService{ 131 parsedUrl, 132 provider, 133 provider.GetMetadata(), 134 schemaLookup, 135 entityLookup, 136 containerLookup, 137 entitySetLookup, 138 propertyLookup, 139 navPropLookup, 140 }, nil 141 } 142 143 // The default handler for parsing requests as GoDataRequests, passing them 144 // to a GoData provider, and then building a response. 145 func (service *GoDataService) GoDataHTTPHandler(w http.ResponseWriter, r *http.Request) { 146 ctx := context.Background() 147 request, err := ParseRequest(ctx, r.URL.Path, r.URL.Query()) 148 149 if err != nil { 150 panic(err) // TODO: return proper error 151 } 152 153 // Semanticize all tokens in the request, connecting them with their 154 // corresponding types in the service 155 err = request.SemanticizeRequest(service) 156 157 if err != nil { 158 panic(err) // TODO: return proper error 159 } 160 161 // TODO: differentiate GET and POST requests 162 var response []byte = []byte{} 163 if request.RequestKind == RequestKindMetadata { 164 response, err = service.buildMetadataResponse(request) 165 } else if request.RequestKind == RequestKindService { 166 response, err = service.buildServiceResponse(request) 167 } else if request.RequestKind == RequestKindCollection { 168 response, err = service.buildCollectionResponse(request) 169 } else if request.RequestKind == RequestKindEntity { 170 response, err = service.buildEntityResponse(request) 171 } else if request.RequestKind == RequestKindProperty { 172 response, err = service.buildPropertyResponse(request) 173 } else if request.RequestKind == RequestKindPropertyValue { 174 response, err = service.buildPropertyValueResponse(request) 175 } else if request.RequestKind == RequestKindCount { 176 response, err = service.buildCountResponse(request) 177 } else if request.RequestKind == RequestKindRef { 178 response, err = service.buildRefResponse(request) 179 } else { 180 err = NotImplementedError("Request type not understood.") 181 } 182 183 if err != nil { 184 panic(err) // TODO: return proper error 185 } 186 187 _, err = w.Write(response) 188 if err != nil { 189 panic(err) // TODO: return proper error 190 } 191 } 192 193 func (service *GoDataService) buildMetadataResponse(request *GoDataRequest) ([]byte, error) { 194 return service.Metadata.Bytes() 195 } 196 197 func (service *GoDataService) buildServiceResponse(request *GoDataRequest) ([]byte, error) { 198 // TODO 199 return nil, NotImplementedError("Service responses are not implemented yet.") 200 } 201 202 func (service *GoDataService) buildCollectionResponse(request *GoDataRequest) ([]byte, error) { 203 response := &GoDataResponse{Fields: map[string]*GoDataResponseField{}} 204 // get request from provider 205 responses := make(chan *providerChannelResponse) 206 go func() { 207 result, err := service.Provider.GetEntityCollection(request) 208 responses <- &providerChannelResponse{result, err} 209 close(responses) 210 }() 211 212 if bool(*request.Query.Count) { 213 // if count is true, also include the count result 214 counts := make(chan *providerChannelResponse) 215 216 go func() { 217 result, err := service.Provider.GetCount(request) 218 counts <- &providerChannelResponse{&GoDataResponseField{result}, err} 219 close(counts) 220 }() 221 222 r := <-counts 223 if r.Error != nil { 224 return nil, r.Error 225 } 226 227 response.Fields[ODataFieldCount] = r.Field 228 } 229 // build context URL 230 context := request.LastSegment.SemanticReference.(*GoDataEntitySet).Name 231 path, err := url.Parse("./$metadata#" + context) 232 if err != nil { 233 return nil, err 234 } 235 contextUrl := service.BaseUrl.ResolveReference(path).String() 236 response.Fields[ODataFieldContext] = &GoDataResponseField{Value: contextUrl} 237 238 // wait for a response from the provider 239 r := <-responses 240 241 if r.Error != nil { 242 return nil, r.Error 243 } 244 245 response.Fields[ODataFieldValue] = r.Field 246 247 return response.Json() 248 } 249 250 func (service *GoDataService) buildEntityResponse(request *GoDataRequest) ([]byte, error) { 251 // get request from provider 252 responses := make(chan *providerChannelResponse) 253 go func() { 254 result, err := service.Provider.GetEntity(request) 255 responses <- &providerChannelResponse{result, err} 256 close(responses) 257 }() 258 259 // build context URL 260 context := request.LastSegment.SemanticReference.(*GoDataEntitySet).Name 261 path, err := url.Parse("./$metadata#" + context + "/$entity") 262 if err != nil { 263 return nil, err 264 } 265 contextUrl := service.BaseUrl.ResolveReference(path).String() 266 267 // wait for a response from the provider 268 r := <-responses 269 270 if r.Error != nil { 271 return nil, r.Error 272 } 273 274 // Add context field to result and create the response 275 switch r.Field.Value.(type) { 276 case map[string]*GoDataResponseField: 277 fields := r.Field.Value.(map[string]*GoDataResponseField) 278 fields[ODataFieldContext] = &GoDataResponseField{Value: contextUrl} 279 response := &GoDataResponse{Fields: fields} 280 281 return response.Json() 282 default: 283 return nil, InternalServerError("Provider did not return a valid response" + 284 " from GetEntity()") 285 } 286 } 287 288 func (service *GoDataService) buildPropertyResponse(request *GoDataRequest) ([]byte, error) { 289 // TODO 290 return nil, NotImplementedError("Property responses are not implemented yet.") 291 } 292 293 func (service *GoDataService) buildPropertyValueResponse(request *GoDataRequest) ([]byte, error) { 294 // TODO 295 return nil, NotImplementedError("Property value responses are not implemented yet.") 296 } 297 298 func (service *GoDataService) buildCountResponse(request *GoDataRequest) ([]byte, error) { 299 // get request from provider 300 responses := make(chan *providerChannelResponse) 301 go func() { 302 result, err := service.Provider.GetCount(request) 303 responses <- &providerChannelResponse{&GoDataResponseField{result}, err} 304 close(responses) 305 }() 306 307 // wait for a response from the provider 308 r := <-responses 309 310 if r.Error != nil { 311 return nil, r.Error 312 } 313 314 return r.Field.Json() 315 } 316 317 func (service *GoDataService) buildRefResponse(request *GoDataRequest) ([]byte, error) { 318 // TODO 319 return nil, NotImplementedError("Ref responses are not implemented yet.") 320 } 321 322 // Start the service listening on the given address. 323 func (service *GoDataService) ListenAndServe(addr string) { 324 http.HandleFunc("/", service.GoDataHTTPHandler) 325 if err := http.ListenAndServe(addr, nil); err != nil { 326 panic(err) // TODO: return proper error 327 } 328 } 329 330 // Lookup an entity type from the service metadata. Accepts a fully qualified 331 // name, e.g., ODataService.EntityTypeName or, if unambiguous, accepts a 332 // simple identifier, e.g., EntityTypeName. 333 func (service *GoDataService) LookupEntityType(name string) (*GoDataEntityType, error) { 334 // strip "Collection()" and just return the raw entity type 335 // The provider should keep track of what are collections and what aren't 336 if strings.Contains(name, "(") && strings.Contains(name, ")") { 337 name = name[strings.Index(name, "(")+1 : strings.LastIndex(name, ")")] 338 } 339 340 parts := strings.Split(name, ".") 341 entityName := parts[len(parts)-1] 342 // remove entity from the list of parts 343 parts = parts[:len(parts)-1] 344 345 schemas, ok := service.EntityTypeLookup[entityName] 346 if !ok { 347 return nil, BadRequestError("Entity " + name + " does not exist.") 348 } 349 350 if len(parts) > 0 { 351 // namespace is provided 352 entity, ok := schemas[parts[len(parts)-1]] 353 if !ok { 354 return nil, BadRequestError("Entity " + name + " not found in given namespace.") 355 } 356 return entity, nil 357 } else { 358 // no namespace, just return the first one 359 360 // throw error if ambiguous 361 if len(schemas) > 1 { 362 return nil, BadRequestError("Entity " + name + " is ambiguous. Please provide a namespace.") 363 } 364 365 for _, v := range schemas { 366 return v, nil 367 } 368 } 369 370 // If this happens, that's very bad 371 return nil, BadRequestError("No schema lookup found for entity " + name) 372 } 373 374 // Lookup an entity set from the service metadata. Accepts a fully qualified 375 // name, e.g., ODataService.ContainerName.EntitySetName, 376 // ContainerName.EntitySetName or, if unambiguous, accepts a simple identifier, 377 // e.g., EntitySetName. 378 func (service *GoDataService) LookupEntitySet(name string) (*GoDataEntitySet, error) { 379 parts := strings.Split(name, ".") 380 setName := parts[len(parts)-1] 381 // remove entity set from the list of parts 382 parts = parts[:len(parts)-1] 383 384 containers, ok := service.EntitySetLookup[setName] 385 if !ok { 386 return nil, BadRequestError("Entity set " + name + " does not exist.") 387 } 388 389 if len(parts) > 0 { 390 // container is provided 391 schemas, ok := containers[parts[len(parts)-1]] 392 if !ok { 393 return nil, BadRequestError("Container " + name + " not found.") 394 } 395 396 // remove container name from the list of parts 397 parts = parts[:len(parts)-1] 398 399 if len(parts) > 0 { 400 // schema is provided 401 set, ok := schemas[parts[len(parts)-1]] 402 if !ok { 403 return nil, BadRequestError("Entity set " + name + " not found.") 404 } 405 return set, nil 406 } else { 407 // no schema is provided 408 409 if len(schemas) > 1 { 410 // container is ambiguous 411 return nil, BadRequestError("Entity set " + name + " is ambiguous. Please provide fully qualified name.") 412 } 413 414 // there should be one schema, if not then something went horribly wrong 415 for _, set := range schemas { 416 return set, nil 417 } 418 } 419 420 } else { 421 // no container is provided 422 423 // return error if entity set is ambiguous 424 if len(containers) > 1 { 425 return nil, BadRequestError("Entity set " + name + " is ambiguous. Please provide fully qualified name.") 426 } 427 428 // find the first schema, it will be the only one 429 for _, schemas := range containers { 430 if len(schemas) > 1 { 431 // container is ambiguous 432 return nil, BadRequestError("Entity set " + name + " is ambiguous. Please provide fully qualified name.") 433 } 434 435 // there should be one schema, if not then something went horribly wrong 436 for _, set := range schemas { 437 return set, nil 438 } 439 } 440 } 441 return nil, BadRequestError("Entity set " + name + " not found.") 442 }