github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/extension/otto/otto.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 otto
    17  
    18  import (
    19  	"fmt"
    20  	"time"
    21  
    22  	"github.com/cloudwan/gohan/db"
    23  	"github.com/cloudwan/gohan/db/transaction"
    24  	ext "github.com/cloudwan/gohan/extension"
    25  	"github.com/cloudwan/gohan/schema"
    26  	"github.com/cloudwan/gohan/server/middleware"
    27  	"github.com/cloudwan/gohan/sync"
    28  
    29  	"github.com/ddliu/motto"
    30  	"github.com/robertkrimen/otto"
    31  
    32  	"reflect"
    33  	//Import otto underscore lib
    34  	_ "github.com/robertkrimen/otto/underscore"
    35  )
    36  
    37  var inits = []func(env *Environment){}
    38  var modules = map[string]interface{}{}
    39  
    40  //RegisterModule registers modules
    41  func RegisterModule(name string, module interface{}) {
    42  	modules[name] = module
    43  }
    44  
    45  //RequireModule returns module
    46  func RequireModule(name string) (interface{}, error) {
    47  	v, ok := modules[name]
    48  	if ok {
    49  		return v, nil
    50  	} else {
    51  		return nil, fmt.Errorf("Module %s not found in Otto", name)
    52  	}
    53  }
    54  
    55  //Environment javascript based environment for gohan extension
    56  type Environment struct {
    57  	Name      string
    58  	VM        *motto.Motto
    59  	DataStore db.DB
    60  	timelimit time.Duration
    61  	Identity  middleware.IdentityService
    62  	Sync      sync.Sync
    63  }
    64  
    65  //NewEnvironment create new gohan extension environment based on context
    66  func NewEnvironment(name string, dataStore db.DB, identity middleware.IdentityService,
    67                      timelimit time.Duration, sync sync.Sync) *Environment {
    68  	vm := motto.New()
    69  	vm.Interrupt = make(chan func(), 1)
    70  	env := &Environment{
    71  		Name:      name,
    72  		VM:        vm,
    73  		DataStore: dataStore,
    74  		Identity:  identity,
    75  		timelimit: timelimit,
    76  		Sync:      sync,
    77  	}
    78  	env.SetUp()
    79  	return env
    80  }
    81  
    82  //SetUp initialize environment
    83  func (env *Environment) SetUp() {
    84  	for _, init := range inits {
    85  		init(env)
    86  	}
    87  }
    88  
    89  //RegisterInit registers init code
    90  func RegisterInit(init func(env *Environment)) {
    91  	inits = append(inits, init)
    92  }
    93  
    94  //Load loads script for environment
    95  func (env *Environment) Load(source, code string) error {
    96  	vm := env.VM
    97  	script, err := vm.Compile(source, code)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	_, err = vm.Otto.Run(script)
   102  
   103  	if err != nil {
   104  		return err
   105  	}
   106  	return nil
   107  }
   108  
   109  //RegisterObject register new object for VM
   110  func (env *Environment) RegisterObject(objectID string, object interface{}) {
   111  	env.VM.Set(objectID, object)
   112  }
   113  
   114  //LoadExtensionsForPath loads extensions for specific path
   115  func (env *Environment) LoadExtensionsForPath(extensions []*schema.Extension, path string) error {
   116  	for _, extension := range extensions {
   117  		if extension.Match(path) {
   118  			code := extension.Code
   119  			if extension.CodeType != "javascript" {
   120  				continue
   121  			}
   122  
   123  			script, err := env.VM.Compile(extension.URL, code)
   124  			if err != nil {
   125  				return err
   126  			}
   127  			_, err = env.VM.Otto.Run(script)
   128  			if err != nil {
   129  				return err
   130  			}
   131  
   132  		}
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  func convertNilsToNulls(object interface{}) {
   139  	switch object := object.(type) {
   140  	case map[string]interface{}:
   141  		for key, obj := range object {
   142  			switch obj := obj.(type) {
   143  			case map[string]interface{}:
   144  				convertNilsToNulls(obj)
   145  			case []interface{}:
   146  				convertNilsToNulls(obj)
   147  			case nil:
   148  				object[key] = otto.NullValue()
   149  			}
   150  		}
   151  	case []interface{}:
   152  		for key, obj := range object {
   153  			switch obj := obj.(type) {
   154  			case map[string]interface{}:
   155  				convertNilsToNulls(obj)
   156  			case []interface{}:
   157  				convertNilsToNulls(obj)
   158  			case nil:
   159  				object[key] = otto.NullValue()
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  //HandleEvent handles event
   166  func (env *Environment) HandleEvent(event string, context map[string]interface{}) (err error) {
   167  	vm := env.VM
   168  	context["event_type"] = event
   169  	var halt = fmt.Errorf("exceed timeout for extension execution")
   170  
   171  	defer func() {
   172  		if caught := recover(); caught != nil {
   173  			if caughtError, ok := caught.(error); ok {
   174  				if caughtError.Error() == halt.Error() {
   175  					log.Error(halt.Error())
   176  					err = halt
   177  					return
   178  				}
   179  			}
   180  
   181  			panic(caught) // Something else happened, repanic!
   182  		}
   183  	}()
   184  	timer := time.NewTimer(env.timelimit)
   185  	successCh := make(chan bool)
   186  	go func() {
   187  		for {
   188  			select {
   189  			case <-timer.C:
   190  				vm.Interrupt <- func() {
   191  					panic(halt)
   192  				}
   193  				return
   194  			case <-successCh:
   195  				// extension executed successfully
   196  				return
   197  			}
   198  		}
   199  	}()
   200  
   201  	//FIXME(timorl): This is needed only because of a bug in Otto, where nils are converted to undefineds instead of nulls.
   202  	convertNilsToNulls(context)
   203  	contextInVM, err := vm.ToValue(context)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	_, err = vm.Call("gohan_handle_event", nil, event, contextInVM)
   208  	for key, value := range context {
   209  		context[key] = ConvertOttoToGo(value)
   210  	}
   211  	if err != nil {
   212  		switch err.(type) {
   213  		case *otto.Error:
   214  			ottoErr := err.(*otto.Error)
   215  			err = fmt.Errorf("%s: %s", event, ottoErr.String())
   216  		default:
   217  			err = fmt.Errorf("%s: %s", event, err.Error())
   218  		}
   219  	}
   220  
   221  	timer.Stop()
   222  	successCh <- true
   223  	if err != nil {
   224  		return err
   225  	}
   226  	return err
   227  }
   228  
   229  //Clone makes clone of the environment
   230  func (env *Environment) Clone() ext.Environment {
   231  	newEnv := NewEnvironment(env.Name, env.DataStore, env.Identity, env.timelimit, env.Sync)
   232  	newEnv.VM.Otto = env.VM.Copy()
   233  	return newEnv
   234  }
   235  
   236  //GetOrCreateTransaction gets transaction from otto value or creates new is otto value is null
   237  func (env *Environment) GetOrCreateTransaction(value otto.Value) (transaction.Transaction, bool, error) {
   238  	if !value.IsNull() {
   239  		tx, err := GetTransaction(value)
   240  		return tx, false, err
   241  	}
   242  	dataStore := env.DataStore
   243  	tx, err := dataStore.Begin()
   244  	if err != nil {
   245  		return nil, false, fmt.Errorf("Error creating transaction: %v", err.Error())
   246  	}
   247  	return tx, true, nil
   248  }
   249  
   250  func (env *Environment) ClearEnvironment()  {
   251  	env.Sync.Close()
   252  	env.DataStore.Close()
   253  }
   254  
   255  func throwOtto(call *otto.FunctionCall, exceptionName string, arguments ...interface{}) {
   256  	exception, _ := call.Otto.Call("new "+exceptionName, nil, arguments...)
   257  	panic(exception)
   258  }
   259  
   260  //ThrowOttoException throws a JavaScript exception that will be passed to Otto
   261  func ThrowOttoException(call *otto.FunctionCall, format string, arguments ...interface{}) {
   262  	throwOtto(call, "Error", fmt.Sprintf(format, arguments...))
   263  }
   264  
   265  //VerifyCallArguments verify number of calles
   266  func VerifyCallArguments(call *otto.FunctionCall, functionName string, expectedArgumentsCount int) {
   267  	if len(call.ArgumentList) != expectedArgumentsCount {
   268  		ThrowOttoException(call, "Expected %d arguments in %s call, %d arguments given",
   269  			expectedArgumentsCount, functionName, len(call.ArgumentList))
   270  	}
   271  }
   272  
   273  const wrongArgumentType string = "Argument '%v' should be of type '%s'"
   274  
   275  //GetString gets string from otto value
   276  func GetString(value otto.Value) (string, error) {
   277  	rawString, _ := value.Export()
   278  	result, ok := rawString.(string)
   279  	if !ok {
   280  		return "", fmt.Errorf(wrongArgumentType, rawString, "string")
   281  	}
   282  	return result, nil
   283  }
   284  
   285  //GetMap gets map[string]interface{} from otto value
   286  func GetMap(value otto.Value) (map[string]interface{}, error) {
   287  	rawMap, _ := value.Export()
   288  	result, ok := rawMap.(map[string]interface{})
   289  	if !ok {
   290  		return map[string]interface{}{}, fmt.Errorf(wrongArgumentType, rawMap, "Object")
   291  	}
   292  	for key, value := range result {
   293  		result[key] = ConvertOttoToGo(value)
   294  	}
   295  	return result, nil
   296  }
   297  
   298  //GetTransaction gets Transaction from otto value
   299  func GetTransaction(value otto.Value) (transaction.Transaction, error) {
   300  	rawTransaction, _ := value.Export()
   301  	result, ok := rawTransaction.(transaction.Transaction)
   302  	if !ok {
   303  		return nil, fmt.Errorf(wrongArgumentType, rawTransaction, "Transaction")
   304  	}
   305  	return result, nil
   306  }
   307  
   308  //GetAuthorization gets Transaction from otto value
   309  func GetAuthorization(value otto.Value) (schema.Authorization, error) {
   310  	rawAuthorization, _ := value.Export()
   311  	result, ok := rawAuthorization.(schema.Authorization)
   312  	if !ok {
   313  		return nil, fmt.Errorf(wrongArgumentType, rawAuthorization, "Authorization")
   314  	}
   315  	return result, nil
   316  }
   317  
   318  //GetBool gets bool from otto value
   319  func GetBool(value otto.Value) (bool, error) {
   320  	rawBool, _ := value.Export()
   321  	result, ok := rawBool.(bool)
   322  	if !ok {
   323  		return false, fmt.Errorf(wrongArgumentType, rawBool, "bool")
   324  	}
   325  	return result, nil
   326  }
   327  
   328  //GetList gets []interface{} from otto value
   329  func GetList(value otto.Value) ([]interface{}, error) {
   330  	rawSlice, err := value.Export()
   331  	result := make([]interface{}, 0)
   332  	if rawSlice == nil || err != nil {
   333  		return result, err
   334  	}
   335  	typeOfSlice := reflect.TypeOf(rawSlice)
   336  	if typeOfSlice.Kind() != reflect.Array && typeOfSlice.Kind() != reflect.Slice {
   337  		return result, fmt.Errorf(wrongArgumentType, value, "array")
   338  	}
   339  	list := reflect.ValueOf(rawSlice)
   340  	for i := 0; i < list.Len(); i++ {
   341  		result = append(result, ConvertOttoToGo(list.Index(i).Interface()))
   342  	}
   343  
   344  	return result, err
   345  }
   346  
   347  //GetStringList gets []string  from otto value
   348  func GetStringList(value otto.Value) ([]string, error) {
   349  	var ok bool
   350  	var rawSlice []interface{}
   351  	var stringSlice []string
   352  
   353  	rawData, _ := value.Export()
   354  	rawSlice, ok = rawData.([]interface{})
   355  
   356  	if ok && len(rawSlice) == 0 {
   357  		return []string{}, nil
   358  	}
   359  
   360  	stringSlice, ok = rawData.([]string)
   361  
   362  	if !ok {
   363  		return make([]string, 0), fmt.Errorf(wrongArgumentType, rawData, "array of strings")
   364  	}
   365  
   366  	return stringSlice, nil
   367  }
   368  
   369  //GetInt64 gets int64 from otto value
   370  func GetInt64(value otto.Value) (result int64, err error) {
   371  	result, err = value.ToInteger()
   372  	if err != nil {
   373  		err = fmt.Errorf(wrongArgumentType, value, "int64")
   374  	}
   375  	return
   376  }
   377  
   378  //ConvertOttoToGo ...
   379  func ConvertOttoToGo(value interface{}) interface{} {
   380  	ottoValue, ok := value.(otto.Value)
   381  	if ok {
   382  		exportedValue, err := ottoValue.Export()
   383  		if err != nil {
   384  			return err
   385  		}
   386  		return ConvertOttoToGo(exportedValue)
   387  	}
   388  	mapValue, ok := value.(map[string]interface{})
   389  	if ok {
   390  		for key, value := range mapValue {
   391  			mapValue[key] = ConvertOttoToGo(value)
   392  		}
   393  		return mapValue
   394  	}
   395  	listValue, ok := value.([]interface{})
   396  	if ok {
   397  		for key, value := range listValue {
   398  			listValue[key] = ConvertOttoToGo(value)
   399  		}
   400  		return listValue
   401  	}
   402  	return value
   403  }
   404  
   405  func getSchema(schemaID string) (*schema.Schema, error) {
   406  	manager := schema.GetManager()
   407  	schema, ok := manager.Schema(schemaID)
   408  	if !ok {
   409  		return nil, fmt.Errorf(unknownSchemaErrorMesssageFormat, schemaID)
   410  	}
   411  	return schema, nil
   412  }