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 }