github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/server/resources/resource_management.go (about)

     1  // Copyright (C) 2015 NTT Innovation Institute, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    12  // implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package resources
    17  
    18  import (
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/cloudwan/gohan/db"
    23  	"github.com/cloudwan/gohan/db/pagination"
    24  	"github.com/cloudwan/gohan/db/transaction"
    25  	"github.com/cloudwan/gohan/extension"
    26  
    27  	"github.com/cloudwan/gohan/schema"
    28  	"github.com/cloudwan/gohan/server/middleware"
    29  	"github.com/twinj/uuid"
    30  )
    31  
    32  //ResourceProblem describes the kind of problem that occurred during resource manipulation.
    33  type ResourceProblem int
    34  
    35  //The possible resource problems
    36  const (
    37  	InternalServerError ResourceProblem = iota
    38  	WrongQuery
    39  	WrongData
    40  	NotFound
    41  	DeleteFailed
    42  	CreateFailed
    43  	UpdateFailed
    44  	hlsearch
    45  
    46  	Unauthorized
    47  )
    48  
    49  // ResourceError is created when an anticipated problem has occurred during resource manipulations.
    50  // It contains the original error, a message to the user and an integer indicating the type of the problem.
    51  type ResourceError struct {
    52  	error
    53  	Message string
    54  	Problem ResourceProblem
    55  }
    56  
    57  //NewResourceError returns a new resource error
    58  func NewResourceError(err error, message string, problem ResourceProblem) ResourceError {
    59  	return ResourceError{err, message, problem}
    60  }
    61  
    62  // ExtensionError is created when a problem has occurred during event handling. It contains the information
    63  // required to reraise the javascript exception that caused this error.
    64  type ExtensionError struct {
    65  	error
    66  	ExceptionInfo map[string]interface{}
    67  }
    68  
    69  //InTransaction executes function in the db transaction and set it to the context
    70  func InTransaction(context middleware.Context, dataStore db.DB, level transaction.Type, f func() error) error {
    71  	if context["transaction"] != nil {
    72  		return fmt.Errorf("cannot create nested transaction")
    73  	}
    74  	aTransaction, err := dataStore.Begin()
    75  	if err != nil {
    76  		return fmt.Errorf("cannot create transaction: %v", err)
    77  	}
    78  	defer aTransaction.Close()
    79  	err = aTransaction.SetIsolationLevel(level)
    80  	if err != nil {
    81  		return fmt.Errorf("error when setting isolation level '%s': %s", level, err)
    82  	}
    83  	context["transaction"] = aTransaction
    84  
    85  	err = f()
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	err = aTransaction.Commit()
    91  	if err != nil {
    92  		return fmt.Errorf("commit error : %s", err)
    93  	}
    94  	delete(context, "transaction")
    95  	return nil
    96  }
    97  
    98  // ApplyPolicyForResources applies policy filtering for response
    99  func ApplyPolicyForResources(context middleware.Context, resourceSchema *schema.Schema) error {
   100  	policy := context["policy"].(*schema.Policy)
   101  	rawResponse, ok := context["response"]
   102  	if !ok {
   103  		return fmt.Errorf("No response")
   104  	}
   105  	response, ok := rawResponse.(map[string]interface{})
   106  	if !ok {
   107  		return fmt.Errorf("extension returned invalid JSON: %v", rawResponse)
   108  	}
   109  	resources, ok := response[resourceSchema.Plural].([]interface{})
   110  	if !ok {
   111  		return nil
   112  	}
   113  	data := []interface{}{}
   114  	for _, resource := range resources {
   115  		resourceMap := resource.(map[string]interface{})
   116  		if err := policy.ApplyPropertyConditionFilter(schema.ActionRead, resourceMap, nil); err != nil {
   117  			continue
   118  		}
   119  		data = append(data, policy.RemoveHiddenProperty(resourceMap))
   120  	}
   121  	response[resourceSchema.Plural] = data
   122  	return nil
   123  }
   124  
   125  // ApplyPolicyForResource applies policy filtering for response
   126  func ApplyPolicyForResource(context middleware.Context, resourceSchema *schema.Schema) error {
   127  	policy := context["policy"].(*schema.Policy)
   128  	rawResponse, ok := context["response"]
   129  	if !ok {
   130  		return fmt.Errorf("No response")
   131  	}
   132  	response, ok := rawResponse.(map[string]interface{})
   133  	if !ok {
   134  		return fmt.Errorf("extension returned invalid JSON: %v", rawResponse)
   135  	}
   136  	resource, ok := response[resourceSchema.Singular]
   137  	if !ok {
   138  		return nil
   139  	}
   140  	resourceMap := resource.(map[string]interface{})
   141  	if err := policy.ApplyPropertyConditionFilter(schema.ActionRead, resourceMap, nil); err != nil {
   142  		return err
   143  	}
   144  	response[resourceSchema.Singular] = policy.RemoveHiddenProperty(resourceMap)
   145  
   146  	return nil
   147  }
   148  
   149  //GetResources returns specified resources without calling non in_transaction events
   150  func GetResources(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, filter map[string]interface{}, paginator *pagination.Paginator) error {
   151  	return InTransaction(
   152  		context, dataStore,
   153  		transaction.GetIsolationLevel(resourceSchema, schema.ActionRead),
   154  		func() error {
   155  			return GetResourcesInTransaction(context, resourceSchema, filter, paginator)
   156  		},
   157  	)
   158  }
   159  
   160  //GetResourcesInTransaction returns specified resources without calling non in_transaction events
   161  func GetResourcesInTransaction(context middleware.Context, resourceSchema *schema.Schema, filter map[string]interface{}, paginator *pagination.Paginator) error {
   162  	mainTransaction := context["transaction"].(transaction.Transaction)
   163  	response := map[string]interface{}{}
   164  
   165  	environmentManager := extension.GetManager()
   166  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   167  	if !ok {
   168  		return fmt.Errorf("no environment for schema")
   169  	}
   170  
   171  	if err := extension.HandleEvent(context, environment, "pre_list_in_transaction"); err != nil {
   172  		return err
   173  	}
   174  
   175  	list, total, err := mainTransaction.List(resourceSchema, filter, paginator)
   176  	if err != nil {
   177  		response[resourceSchema.Plural] = []interface{}{}
   178  		context["response"] = response
   179  		return err
   180  	}
   181  
   182  	data := []interface{}{}
   183  	for _, resource := range list {
   184  		data = append(data, resource.Data())
   185  	}
   186  	response[resourceSchema.Plural] = data
   187  
   188  	context["response"] = response
   189  	context["total"] = total
   190  
   191  	if err := extension.HandleEvent(context, environment, "post_list_in_transaction"); err != nil {
   192  		return err
   193  	}
   194  	return nil
   195  }
   196  
   197  //FilterFromQueryParameter makes list filter from query
   198  func FilterFromQueryParameter(resourceSchema *schema.Schema,
   199  	queryParameters map[string][]string) map[string]interface{} {
   200  	filter := map[string]interface{}{}
   201  	for key, value := range queryParameters {
   202  		if _, err := resourceSchema.GetPropertyByID(key); err != nil {
   203  			log.Info("Resource '%s' does not have '%s' property, ignoring filter.",
   204  				resourceSchema.ID, key)
   205  			continue
   206  		}
   207  		filter[key] = value
   208  	}
   209  	return filter
   210  }
   211  
   212  // GetMultipleResources returns all resources specified by the schema and query parameters
   213  func GetMultipleResources(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, queryParameters map[string][]string) error {
   214  	log.Debug("Start get multiple resources!!")
   215  	auth := context["auth"].(schema.Authorization)
   216  	policy, err := loadPolicy(context, "read", resourceSchema.GetPluralURL(), auth)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	filter := FilterFromQueryParameter(resourceSchema, queryParameters)
   222  
   223  	if policy.RequireOwner() {
   224  		filter["tenant_id"] = policy.GetTenantIDFilter(schema.ActionRead, auth.TenantID())
   225  	}
   226  	filter = policy.RemoveHiddenProperty(filter)
   227  
   228  	paginator, err := pagination.FromURLQuery(resourceSchema, queryParameters)
   229  	if err != nil {
   230  		return ResourceError{err, err.Error(), WrongQuery}
   231  	}
   232  	context["policy"] = policy
   233  
   234  	environmentManager := extension.GetManager()
   235  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   236  	if !ok {
   237  		return fmt.Errorf("No environment for schema")
   238  	}
   239  	if err := extension.HandleEvent(context, environment, "pre_list"); err != nil {
   240  		return err
   241  	}
   242  	if rawResponse, ok := context["response"]; ok {
   243  		if _, ok := rawResponse.(map[string]interface{}); ok {
   244  			return nil
   245  		}
   246  		return fmt.Errorf("extension returned invalid JSON: %v", rawResponse)
   247  	}
   248  
   249  	if err := GetResources(context, dataStore, resourceSchema, filter, paginator); err != nil {
   250  		return err
   251  	}
   252  
   253  	if err := extension.HandleEvent(context, environment, "post_list"); err != nil {
   254  		return err
   255  	}
   256  
   257  	if err := ApplyPolicyForResources(context, resourceSchema); err != nil {
   258  		return err
   259  	}
   260  
   261  	return nil
   262  }
   263  
   264  // GetSingleResource returns the resource specified by the schema and ID
   265  func GetSingleResource(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, resourceID string) error {
   266  	context["id"] = resourceID
   267  	auth := context["auth"].(schema.Authorization)
   268  	policy, err := loadPolicy(context, "read", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth)
   269  	if err != nil {
   270  		return err
   271  	}
   272  	context["policy"] = policy
   273  
   274  	environmentManager := extension.GetManager()
   275  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   276  	if !ok {
   277  		return fmt.Errorf("No environment for schema")
   278  	}
   279  	if err := extension.HandleEvent(context, environment, "pre_show"); err != nil {
   280  		return err
   281  	}
   282  	if rawResponse, ok := context["response"]; ok {
   283  		if _, ok := rawResponse.(map[string]interface{}); ok {
   284  			return nil
   285  		}
   286  		return fmt.Errorf("extension returned invalid JSON: %v", rawResponse)
   287  	}
   288  
   289  	if err := InTransaction(
   290  		context, dataStore,
   291  		transaction.GetIsolationLevel(resourceSchema, schema.ActionRead),
   292  		func() error {
   293  			return GetSingleResourceInTransaction(context, resourceSchema, resourceID, policy.GetTenantIDFilter(schema.ActionRead, auth.TenantID()))
   294  		},
   295  	); err != nil {
   296  		return err
   297  	}
   298  
   299  	if err := extension.HandleEvent(context, environment, "post_show"); err != nil {
   300  		return err
   301  	}
   302  	if err := ApplyPolicyForResource(context, resourceSchema); err != nil {
   303  		return ResourceError{err, "", NotFound}
   304  	}
   305  	return nil
   306  }
   307  
   308  //GetSingleResourceInTransaction get resource in single transaction
   309  func GetSingleResourceInTransaction(context middleware.Context, resourceSchema *schema.Schema, resourceID string, tenantIDs []string) (err error) {
   310  	mainTransaction := context["transaction"].(transaction.Transaction)
   311  	environmentManager := extension.GetManager()
   312  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   313  	if !ok {
   314  		return fmt.Errorf("no environment for schema")
   315  	}
   316  
   317  	if err := extension.HandleEvent(context, environment, "pre_show_in_transaction"); err != nil {
   318  		return err
   319  	}
   320  	if rawResponse, ok := context["response"]; ok {
   321  		if _, ok := rawResponse.(map[string]interface{}); ok {
   322  			return nil
   323  		}
   324  		return fmt.Errorf("extension returned invalid JSON: %v", rawResponse)
   325  	}
   326  	filter := transaction.IDFilter(resourceID)
   327  	if tenantIDs != nil {
   328  		filter["tenant_id"] = tenantIDs
   329  	}
   330  	object, err := mainTransaction.Fetch(resourceSchema, filter)
   331  
   332  	if err != nil || object == nil {
   333  		return ResourceError{err, "", NotFound}
   334  	}
   335  
   336  	response := map[string]interface{}{}
   337  	response[resourceSchema.Singular] = object.Data()
   338  	context["response"] = response
   339  
   340  	if err := extension.HandleEvent(context, environment, "post_show_in_transaction"); err != nil {
   341  		return err
   342  	}
   343  	return
   344  }
   345  
   346  // CreateOrUpdateResource updates resource if it existed and otherwise creates it and returns true.
   347  func CreateOrUpdateResource(
   348  	context middleware.Context,
   349  	dataStore db.DB, identityService middleware.IdentityService,
   350  	resourceSchema *schema.Schema,
   351  	resourceID string, dataMap map[string]interface{},
   352  ) (bool, error) {
   353  	auth := context["auth"].(schema.Authorization)
   354  
   355  	//LoadPolicy
   356  	policy, err := loadPolicy(context, "update", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth)
   357  	if err != nil {
   358  		return false, err
   359  	}
   360  
   361  	preTransaction, err := dataStore.Begin()
   362  	if err != nil {
   363  		return false, fmt.Errorf("cannot create transaction: %v", err)
   364  	}
   365  	tenantIDs := policy.GetTenantIDFilter(schema.ActionUpdate, auth.TenantID())
   366  	filter := transaction.IDFilter(resourceID)
   367  	if tenantIDs != nil {
   368  		filter["tenant_id"] = tenantIDs
   369  	}
   370  	_, fetchErr := preTransaction.Fetch(resourceSchema, filter)
   371  	preTransaction.Close()
   372  
   373  	if fetchErr != nil {
   374  		dataMap["id"] = resourceID
   375  		if err := CreateResource(context, dataStore, identityService, resourceSchema, dataMap); err != nil {
   376  			return false, err
   377  		}
   378  		return true, err
   379  	}
   380  
   381  	return false, UpdateResource(context, dataStore, identityService, resourceSchema, resourceID, dataMap)
   382  }
   383  
   384  // CreateResource creates the resource specified by the schema and dataMap
   385  func CreateResource(
   386  	context middleware.Context,
   387  	dataStore db.DB,
   388  	identityService middleware.IdentityService,
   389  	resourceSchema *schema.Schema,
   390  	dataMap map[string]interface{},
   391  ) error {
   392  	manager := schema.GetManager()
   393  	// Load environment
   394  	environmentManager := extension.GetManager()
   395  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   396  
   397  	if !ok {
   398  		return fmt.Errorf("No environment for schema")
   399  	}
   400  	auth := context["auth"].(schema.Authorization)
   401  
   402  	//LoadPolicy
   403  	policy, err := loadPolicy(context, "create", resourceSchema.GetPluralURL(), auth)
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	_, err = resourceSchema.GetPropertyByID("tenant_id")
   409  	if _, ok := dataMap["tenant_id"]; err == nil && !ok {
   410  		dataMap["tenant_id"] = context["tenant_id"]
   411  	}
   412  
   413  	if tenantID, ok := dataMap["tenant_id"]; ok && tenantID != nil {
   414  		dataMap["tenant_name"], err = identityService.GetTenantName(tenantID.(string))
   415  		if err != nil {
   416  			return ResourceError{err, err.Error(), Unauthorized}
   417  		}
   418  	}
   419  
   420  	//Apply policy for api input
   421  	err = policy.Check(schema.ActionCreate, auth, dataMap)
   422  	if err != nil {
   423  		return ResourceError{err, err.Error(), Unauthorized}
   424  	}
   425  	delete(dataMap, "tenant_name")
   426  
   427  	// apply property filter
   428  	err = policy.ApplyPropertyConditionFilter(schema.ActionCreate, dataMap, nil)
   429  	if err != nil {
   430  		return ResourceError{err, err.Error(), Unauthorized}
   431  	}
   432  	context["resource"] = dataMap
   433  	if id, ok := dataMap["id"]; !ok || id == "" {
   434  		dataMap["id"] = uuid.NewV4().String()
   435  	}
   436  	context["id"] = dataMap["id"]
   437  
   438  	if err := extension.HandleEvent(context, environment, "pre_create"); err != nil {
   439  		return err
   440  	}
   441  
   442  	if resourceData, ok := context["resource"].(map[string]interface{}); ok {
   443  		dataMap = resourceData
   444  	}
   445  
   446  	//Validation
   447  	err = resourceSchema.ValidateOnCreate(dataMap)
   448  	if err != nil {
   449  		return ResourceError{err, fmt.Sprintf("Validation error: %s", err), WrongData}
   450  	}
   451  
   452  	resource, err := manager.LoadResource(resourceSchema.ID, dataMap)
   453  	if err != nil {
   454  		return err
   455  	}
   456  
   457  	//Fillup default
   458  	err = resource.PopulateDefaults()
   459  	if err != nil {
   460  		return err
   461  	}
   462  
   463  	context["resource"] = resource.Data()
   464  
   465  	if err := InTransaction(
   466  		context, dataStore,
   467  		transaction.GetIsolationLevel(resourceSchema, schema.ActionCreate),
   468  		func() error {
   469  			return CreateResourceInTransaction(context, resource)
   470  		},
   471  	); err != nil {
   472  		return err
   473  	}
   474  
   475  	if err := extension.HandleEvent(context, environment, "post_create"); err != nil {
   476  		return err
   477  	}
   478  
   479  	if err := ApplyPolicyForResource(context, resourceSchema); err != nil {
   480  		return ResourceError{err, "", Unauthorized}
   481  	}
   482  	return nil
   483  }
   484  
   485  //CreateResourceInTransaction craete db resource model in transaction
   486  func CreateResourceInTransaction(context middleware.Context, resource *schema.Resource) error {
   487  	resourceSchema := resource.Schema()
   488  	mainTransaction := context["transaction"].(transaction.Transaction)
   489  	environmentManager := extension.GetManager()
   490  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   491  	if !ok {
   492  		return fmt.Errorf("No environment for schema")
   493  	}
   494  	if err := extension.HandleEvent(context, environment, "pre_create_in_transaction"); err != nil {
   495  		return err
   496  	}
   497  	if err := mainTransaction.Create(resource); err != nil {
   498  		log.Debug("%s transaction error", err)
   499  		return ResourceError{
   500  			err,
   501  			fmt.Sprintf("Failed to store data in database: %v", err),
   502  			CreateFailed}
   503  	}
   504  
   505  	response := map[string]interface{}{}
   506  	response[resourceSchema.Singular] = resource.Data()
   507  	context["response"] = response
   508  
   509  	if err := extension.HandleEvent(context, environment, "post_create_in_transaction"); err != nil {
   510  		return err
   511  	}
   512  
   513  	return nil
   514  }
   515  
   516  // UpdateResource updates the resource specified by the schema and ID using the dataMap
   517  func UpdateResource(
   518  	context middleware.Context,
   519  	dataStore db.DB, identityService middleware.IdentityService,
   520  	resourceSchema *schema.Schema,
   521  	resourceID string, dataMap map[string]interface{},
   522  ) error {
   523  
   524  	context["id"] = resourceID
   525  
   526  	//load environment
   527  	environmentManager := extension.GetManager()
   528  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   529  	if !ok {
   530  		return fmt.Errorf("No environment for schema")
   531  	}
   532  
   533  	auth := context["auth"].(schema.Authorization)
   534  
   535  	//load policy
   536  	policy, err := loadPolicy(context, "update", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth)
   537  	if err != nil {
   538  		return err
   539  	}
   540  	context["policy"] = policy
   541  
   542  	//fillup default values
   543  	if tenantID, ok := dataMap["tenant_id"]; ok && tenantID != nil {
   544  		dataMap["tenant_name"], err = identityService.GetTenantName(tenantID.(string))
   545  	}
   546  	if err != nil {
   547  		return ResourceError{err, err.Error(), Unauthorized}
   548  	}
   549  
   550  	//check policy
   551  	err = policy.Check(schema.ActionUpdate, auth, dataMap)
   552  	delete(dataMap, "tenant_name")
   553  	if err != nil {
   554  		return ResourceError{err, err.Error(), Unauthorized}
   555  	}
   556  	context["resource"] = dataMap
   557  
   558  	if err := extension.HandleEvent(context, environment, "pre_update"); err != nil {
   559  		return err
   560  	}
   561  
   562  	if resourceData, ok := context["resource"].(map[string]interface{}); ok {
   563  		dataMap = resourceData
   564  	}
   565  
   566  	if err := InTransaction(
   567  		context, dataStore,
   568  		transaction.GetIsolationLevel(resourceSchema, schema.ActionUpdate),
   569  		func() error {
   570  			return UpdateResourceInTransaction(context, resourceSchema, resourceID, dataMap, policy.GetTenantIDFilter(schema.ActionUpdate, auth.TenantID()))
   571  		},
   572  	); err != nil {
   573  		return err
   574  	}
   575  
   576  	if err := extension.HandleEvent(context, environment, "post_update"); err != nil {
   577  		return err
   578  	}
   579  
   580  	if err := ApplyPolicyForResource(context, resourceSchema); err != nil {
   581  		return ResourceError{err, "", NotFound}
   582  	}
   583  	return nil
   584  }
   585  
   586  // UpdateResourceInTransaction updates resource in db in transaction
   587  func UpdateResourceInTransaction(
   588  	context middleware.Context,
   589  	resourceSchema *schema.Schema, resourceID string,
   590  	dataMap map[string]interface{}, tenantIDs []string) error {
   591  
   592  	manager := schema.GetManager()
   593  	mainTransaction := context["transaction"].(transaction.Transaction)
   594  	environmentManager := extension.GetManager()
   595  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   596  	if !ok {
   597  		return fmt.Errorf("No environment for schema")
   598  	}
   599  	filter := transaction.IDFilter(resourceID)
   600  	if tenantIDs != nil {
   601  		filter["tenant_id"] = tenantIDs
   602  	}
   603  	resource, err := mainTransaction.Fetch(
   604  		resourceSchema, filter)
   605  	if err != nil {
   606  		return ResourceError{err, err.Error(), WrongQuery}
   607  	}
   608  
   609  	policy := context["policy"].(*schema.Policy)
   610  	// apply property filter
   611  	err = policy.ApplyPropertyConditionFilter(schema.ActionUpdate, resource.Data(), dataMap)
   612  	if err != nil {
   613  		return ResourceError{err, "", Unauthorized}
   614  	}
   615  
   616  	err = resource.Update(dataMap)
   617  	if err != nil {
   618  		return ResourceError{err, err.Error(), WrongData}
   619  	}
   620  	context["resource"] = resource.Data()
   621  
   622  	if err := extension.HandleEvent(context, environment, "pre_update_in_transaction"); err != nil {
   623  		return err
   624  	}
   625  
   626  	dataMap, ok = context["resource"].(map[string]interface{})
   627  	if !ok {
   628  		return fmt.Errorf("Resource not JSON: %s", err)
   629  	}
   630  	resource, err = manager.LoadResource(resourceSchema.ID, dataMap)
   631  	if err != nil {
   632  		return fmt.Errorf("Loading Resource failed: %s", err)
   633  	}
   634  
   635  	err = mainTransaction.Update(resource)
   636  	if err != nil {
   637  		return ResourceError{err, fmt.Sprintf("Failed to store data in database: %v", err), UpdateFailed}
   638  	}
   639  
   640  	response := map[string]interface{}{}
   641  	response[resourceSchema.Singular] = resource.Data()
   642  	context["response"] = response
   643  
   644  	if err := extension.HandleEvent(context, environment, "post_update_in_transaction"); err != nil {
   645  		return err
   646  	}
   647  
   648  	return nil
   649  }
   650  
   651  // DeleteResource deletes the resource specified by the schema and ID
   652  func DeleteResource(context middleware.Context,
   653  	dataStore db.DB,
   654  	resourceSchema *schema.Schema,
   655  	resourceID string,
   656  ) error {
   657  	context["id"] = resourceID
   658  	environmentManager := extension.GetManager()
   659  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   660  	if !ok {
   661  		return fmt.Errorf("No environment for schema")
   662  	}
   663  	auth := context["auth"].(schema.Authorization)
   664  	policy, err := loadPolicy(context, "delete", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth)
   665  	if err != nil {
   666  		return err
   667  	}
   668  	context["policy"] = policy
   669  	preTransaction, err := dataStore.Begin()
   670  	if err != nil {
   671  		return fmt.Errorf("cannot create transaction: %v", err)
   672  	}
   673  	tenantIDs := policy.GetTenantIDFilter(schema.ActionDelete, auth.TenantID())
   674  	filter := transaction.IDFilter(resourceID)
   675  	if tenantIDs != nil {
   676  		filter["tenant_id"] = tenantIDs
   677  	}
   678  	resource, fetchErr := preTransaction.Fetch(resourceSchema, filter)
   679  	preTransaction.Close()
   680  
   681  	if resource != nil {
   682  		context["resource"] = resource.Data()
   683  	}
   684  
   685  	if err := extension.HandleEvent(context, environment, "pre_delete"); err != nil {
   686  		return err
   687  	}
   688  	if fetchErr != nil {
   689  		return ResourceError{err, "", NotFound}
   690  	}
   691  	if err := InTransaction(
   692  		context, dataStore,
   693  		transaction.GetIsolationLevel(resourceSchema, schema.ActionDelete),
   694  		func() error {
   695  			return DeleteResourceInTransaction(context, resourceSchema, resourceID)
   696  		},
   697  	); err != nil {
   698  		return err
   699  	}
   700  	if err := extension.HandleEvent(context, environment, "post_delete"); err != nil {
   701  		return err
   702  	}
   703  	return nil
   704  }
   705  
   706  //DeleteResourceInTransaction deletes resources in a transaction
   707  func DeleteResourceInTransaction(context middleware.Context, resourceSchema *schema.Schema, resourceID string) error {
   708  	mainTransaction := context["transaction"].(transaction.Transaction)
   709  	environmentManager := extension.GetManager()
   710  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   711  	if !ok {
   712  		return fmt.Errorf("No environment for schema")
   713  	}
   714  
   715  	auth := context["auth"].(schema.Authorization)
   716  	policy := context["policy"].(*schema.Policy)
   717  	tenantIDs := policy.GetTenantIDFilter(schema.ActionDelete, auth.TenantID())
   718  	filter := transaction.IDFilter(resourceID)
   719  	if tenantIDs != nil {
   720  		filter["tenant_id"] = tenantIDs
   721  	}
   722  	resource, err := mainTransaction.Fetch(resourceSchema, filter)
   723  	log.Debug("%s %s", resource, err)
   724  	if err != nil {
   725  		return err
   726  	}
   727  	if resource != nil {
   728  		context["resource"] = resource.Data()
   729  	}
   730  	// apply property filter
   731  	err = policy.ApplyPropertyConditionFilter(schema.ActionUpdate, resource.Data(), nil)
   732  	if err != nil {
   733  		return ResourceError{err, "", Unauthorized}
   734  	}
   735  	if err := extension.HandleEvent(context, environment, "pre_delete_in_transaction"); err != nil {
   736  		return err
   737  	}
   738  
   739  	err = mainTransaction.Delete(resourceSchema, resourceID)
   740  	if err != nil {
   741  		return ResourceError{err, "", DeleteFailed}
   742  	}
   743  
   744  	if err := extension.HandleEvent(context, environment, "post_delete_in_transaction"); err != nil {
   745  		return err
   746  	}
   747  	return nil
   748  }
   749  
   750  // ActionResource runs custom action on resource
   751  func ActionResource(context middleware.Context, dataStore db.DB, identityService middleware.IdentityService,
   752  	resourceSchema *schema.Schema, action schema.Action, resourceID string, data interface{},
   753  ) error {
   754  	actionSchema := action.InputSchema
   755  	context["input"] = data
   756  	context["id"] = resourceID
   757  
   758  	environmentManager := extension.GetManager()
   759  	environment, ok := environmentManager.GetEnvironment(resourceSchema.ID)
   760  	if !ok {
   761  		return fmt.Errorf("No environment for schema")
   762  	}
   763  
   764  	if actionSchema != nil {
   765  		err := resourceSchema.Validate(actionSchema, data)
   766  		if err != nil {
   767  			return ResourceError{err, fmt.Sprintf("Validation error: %s", err), WrongData}
   768  		}
   769  	}
   770  
   771  	err := extension.HandleEvent(context, environment, action.ID)
   772  	if err != nil {
   773  		return err
   774  	}
   775  
   776  	if _, ok := context["response"]; ok {
   777  		return nil
   778  	}
   779  
   780  	return fmt.Errorf("no response")
   781  }
   782  
   783  func loadPolicy(context middleware.Context, action, path string, auth schema.Authorization) (*schema.Policy, error) {
   784  	manager := schema.GetManager()
   785  	policy, role := manager.PolicyValidate(action, path, auth)
   786  	if policy == nil {
   787  		err := fmt.Errorf(fmt.Sprintf("No matching policy: %s %s", action, path))
   788  		return nil, ResourceError{err, err.Error(), Unauthorized}
   789  	}
   790  	context["policy"] = policy
   791  	context["role"] = role
   792  	return policy, nil
   793  }