github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/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 21 "github.com/cloudwan/gohan/db" 22 ext "github.com/cloudwan/gohan/extension" 23 "github.com/cloudwan/gohan/schema" 24 "github.com/cloudwan/gohan/server/middleware" 25 26 "github.com/dop251/otto" 27 28 //Import otto underscore lib 29 _ "github.com/dop251/otto/underscore" 30 ) 31 32 var inits = []func(env *Environment){} 33 34 //GoCallback is type for go based callback 35 36 //Environment javascript based environment for gohan extension 37 type Environment struct { 38 VM *otto.Otto 39 goCallbacks []ext.GoCallback 40 DataStore db.DB 41 Identity middleware.IdentityService 42 } 43 44 //NewEnvironment create new gohan extension environment based on context 45 func NewEnvironment(dataStore db.DB, identity middleware.IdentityService) *Environment { 46 vm := otto.New() 47 env := &Environment{VM: vm, DataStore: dataStore, Identity: identity} 48 env.SetUp() 49 return env 50 } 51 52 //SetUp initialize environment 53 func (env *Environment) SetUp() { 54 for _, init := range inits { 55 init(env) 56 } 57 env.goCallbacks = []ext.GoCallback{} 58 } 59 60 //RegisterInit registers init code 61 func RegisterInit(init func(env *Environment)) { 62 inits = append(inits, init) 63 } 64 65 //Load loads script for environment 66 func (env *Environment) Load(source, code string) error { 67 vm := env.VM 68 script, err := vm.Compile(source, code) 69 if err != nil { 70 return err 71 } 72 _, err = vm.Run(script) 73 if err != nil { 74 return err 75 } 76 return nil 77 } 78 79 //RegisterObject register new object for VM 80 func (env *Environment) RegisterObject(objectID string, object interface{}) { 81 env.VM.Set(objectID, object) 82 } 83 84 //LoadExtensionsForPath for returns extensions for specific path 85 func (env *Environment) LoadExtensionsForPath(extensions []*schema.Extension, path string) error { 86 var err error 87 for _, extension := range extensions { 88 if extension.Match(path) { 89 code := extension.Code 90 if extension.CodeType == "donburi" { 91 err = env.runDonburi(code) 92 if err != nil { 93 return err 94 } 95 } else if extension.CodeType == "go" { 96 callback := ext.GetGoCallback(code) 97 if callback != nil { 98 env.goCallbacks = append(env.goCallbacks, callback) 99 } 100 } else { 101 script, err := env.VM.Compile(extension.URL, code) 102 if err != nil { 103 return err 104 } 105 _, err = env.VM.Run(script) 106 if err != nil { 107 return err 108 } 109 } 110 } 111 } 112 113 return nil 114 } 115 116 func convertNilsToNulls(object interface{}) { 117 switch object := object.(type) { 118 case map[string]interface{}: 119 for key, obj := range object { 120 switch obj := obj.(type) { 121 case map[string]interface{}: 122 convertNilsToNulls(obj) 123 case []interface{}: 124 convertNilsToNulls(obj) 125 case nil: 126 object[key] = otto.NullValue() 127 } 128 } 129 case []interface{}: 130 for key, obj := range object { 131 switch obj := obj.(type) { 132 case map[string]interface{}: 133 convertNilsToNulls(obj) 134 case []interface{}: 135 convertNilsToNulls(obj) 136 case nil: 137 object[key] = otto.NullValue() 138 } 139 } 140 } 141 } 142 143 //HandleEvent handles event 144 func (env *Environment) HandleEvent(event string, context map[string]interface{}) error { 145 vm := env.VM 146 //FIXME(timorl): This is needed only because of a bug in Otto, where nils are converted to undefineds instead of nulls. 147 convertNilsToNulls(context) 148 contextInVM, err := vm.ToValue(context) 149 if err != nil { 150 return err 151 } 152 _, err = vm.Call("gohan_handle_event", nil, event, contextInVM) 153 for key, value := range context { 154 context[key] = ConvertOttoToGo(value) 155 } 156 if err != nil { 157 switch err.(type) { 158 case *otto.Error: 159 ottoErr := err.(*otto.Error) 160 err = fmt.Errorf("%s: %s", event, ottoErr.String()) 161 default: 162 err = fmt.Errorf("%s: %s", event, err.Error()) 163 } 164 } 165 for _, callback := range env.goCallbacks { 166 err = callback(event, context) 167 if err != nil { 168 return err 169 } 170 } 171 return err 172 } 173 174 //Clone makes clone of the environment 175 func (env *Environment) Clone() ext.Environment { 176 newEnv := NewEnvironment(env.DataStore, env.Identity) 177 newEnv.VM = env.VM.Copy() 178 newEnv.goCallbacks = env.goCallbacks 179 return newEnv 180 } 181 182 func throwOtto(call *otto.FunctionCall, exceptionName string, arguments ...interface{}) { 183 exception, _ := call.Otto.Call("new "+exceptionName, nil, arguments...) 184 panic(exception) 185 } 186 187 //ThrowOttoException throws a JavaScript exception that will be passed to Otto 188 func ThrowOttoException(call *otto.FunctionCall, format string, arguments ...interface{}) { 189 throwOtto(call, "Error", fmt.Sprintf(format, arguments...)) 190 } 191 192 //VerifyCallArguments verify number of calles 193 func VerifyCallArguments(call *otto.FunctionCall, functionName string, expectedArgumentsCount int) { 194 if len(call.ArgumentList) != expectedArgumentsCount { 195 ThrowOttoException(call, "Expected %d arguments in %s call, %d arguments given", 196 expectedArgumentsCount, functionName, len(call.ArgumentList)) 197 } 198 } 199 200 //ConvertOttoToGo ... 201 func ConvertOttoToGo(value interface{}) interface{} { 202 ottoValue, ok := value.(otto.Value) 203 if ok { 204 exportedValue, err := ottoValue.Export() 205 if err != nil { 206 return err 207 } 208 return ConvertOttoToGo(exportedValue) 209 } 210 mapValue, ok := value.(map[string]interface{}) 211 if ok { 212 for key, value := range mapValue { 213 mapValue[key] = ConvertOttoToGo(value) 214 } 215 return mapValue 216 } 217 listValue, ok := value.([]interface{}) 218 if ok { 219 for key, value := range listValue { 220 listValue[key] = ConvertOttoToGo(value) 221 } 222 return listValue 223 } 224 return value 225 }