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  }