Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / session / session.go
diff --git a/src/foundation/api/revel/session/session.go b/src/foundation/api/revel/session/session.go
new file mode 100644 (file)
index 0000000..c5696bc
--- /dev/null
@@ -0,0 +1,364 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package session
+
+import (
+       "encoding/hex"
+       "encoding/json"
+       "errors"
+       "github.com/twinj/uuid"
+       "reflect"
+       "strconv"
+       "strings"
+       "time"
+)
+
+const (
+       // The key for the identity of the session
+       SessionIDKey = "_ID"
+       // The expiration date of the session
+       TimestampKey = "_TS"
+       // The value name indicating how long the session should persist - ie should it persist after the browser closes
+       // this is set under the TimestampKey if the session data should expire immediately
+       SessionValueName = "session"
+       // The key container for the json objects of the data, any non strings found in the map will be placed in here
+       // serialized by key using JSON
+       SessionObjectKeyName = "_object_"
+       // The mapped session object
+       SessionMapKeyName = "_map_"
+       // The suffix of the session cookie
+       SessionCookieSuffix = "_SESSION"
+)
+
+// Session data, can be any data, there are reserved keywords used by the storage data
+// SessionIDKey Is the key name for the session
+// TimestampKey Is the time that the session should expire
+//
+type Session map[string]interface{}
+
+func NewSession() Session {
+       return Session{}
+}
+
+// ID retrieves from the cookie or creates a time-based UUID identifying this
+// session.
+func (s Session) ID() string {
+       if sessionIDStr, ok := s[SessionIDKey]; ok {
+               return sessionIDStr.(string)
+       }
+
+       buffer := uuid.NewV4()
+
+       s[SessionIDKey] = hex.EncodeToString(buffer.Bytes())
+       return s[SessionIDKey].(string)
+}
+
+// getExpiration return a time.Time with the session's expiration date.
+// It uses the passed in expireAfterDuration to add with the current time if the timeout is not
+// browser dependent (ie session). If previous session has set to "session", the time returned is time.IsZero()
+func (s Session) GetExpiration(expireAfterDuration time.Duration) time.Time {
+       if expireAfterDuration == 0 || s[TimestampKey] == SessionValueName {
+               // Expire after closing browser
+               return time.Time{}
+       }
+       return time.Now().Add(expireAfterDuration)
+}
+
+// SetNoExpiration sets session to expire when browser session ends
+func (s Session) SetNoExpiration() {
+       s[TimestampKey] = SessionValueName
+}
+
+// SetDefaultExpiration sets session to expire after default duration
+func (s Session) SetDefaultExpiration() {
+       delete(s, TimestampKey)
+}
+
+// sessionTimeoutExpiredOrMissing returns a boolean of whether the session
+// cookie is either not present or present but beyond its time to live; i.e.,
+// whether there is not a valid session.
+func (s Session) SessionTimeoutExpiredOrMissing() bool {
+       if exp, present := s[TimestampKey]; !present {
+               return true
+       } else if exp == SessionValueName {
+               return false
+       } else if expInt, _ := strconv.Atoi(exp.(string)); int64(expInt) < time.Now().Unix() {
+               return true
+       }
+       return false
+}
+
+// Constant error if session value is not found
+var SESSION_VALUE_NOT_FOUND = errors.New("Session value not found")
+
+// Get an object or property from the session
+// it may be embedded inside the session.
+func (s Session) Get(key string) (newValue interface{}, err error) {
+       // First check to see if it is in the session
+       if v, found := s[key]; found {
+               return v, nil
+       }
+       return s.GetInto(key, nil, false)
+}
+
+// Get into the specified value.
+// If value exists in the session it will just return the value
+func (s Session) GetInto(key string, target interface{}, force bool) (result interface{}, err error) {
+       if v, found := s[key]; found && !force {
+               return v, nil
+       }
+       splitKey := strings.Split(key, ".")
+       rootKey := splitKey[0]
+
+       // Force always recreates the object from the session data map
+       if force {
+               if target == nil {
+                       if result, err = s.sessionDataFromMap(key); err != nil {
+                               return
+                       }
+               } else if result, err = s.sessionDataFromObject(rootKey, target); err != nil {
+                       return
+               }
+
+               return s.getNestedProperty(splitKey, result)
+       }
+
+       // Attempt to find the key in the session, this is the most generalized form
+       v, found := s[rootKey]
+       if !found {
+               if target == nil {
+                       // Try to fetch it from the session
+
+                       if v, err = s.sessionDataFromMap(rootKey); err != nil {
+                               return
+                       }
+               } else if v, err = s.sessionDataFromObject(rootKey, target); err != nil {
+                       return
+               }
+       }
+
+       return s.getNestedProperty(splitKey, v)
+}
+
+// Returns the default value if the key is not found
+func (s Session) GetDefault(key string, value interface{}, defaultValue interface{}) interface{} {
+       v, e := s.GetInto(key, value, false)
+       if e != nil {
+               v = defaultValue
+       }
+       return v
+}
+
+// Extract the values from the session
+func (s Session) GetProperty(key string, value interface{}) (interface{}, error) {
+       // Capitalize the first letter
+       key = strings.Title(key)
+
+       sessionLog.Info("getProperty", "key", key, "value", value)
+
+       // For a map it is easy
+       if reflect.TypeOf(value).Kind() == reflect.Map {
+               val := reflect.ValueOf(value)
+               valueOf := val.MapIndex(reflect.ValueOf(key))
+               if valueOf == reflect.Zero(reflect.ValueOf(value).Type()) {
+                       return nil, nil
+               }
+               //idx := val.MapIndex(reflect.ValueOf(key))
+               if !valueOf.IsValid() {
+                       return nil, nil
+               }
+
+               return valueOf.Interface(), nil
+       }
+
+       objValue := s.reflectValue(value)
+       field := objValue.FieldByName(key)
+       if !field.IsValid() {
+               return nil, SESSION_VALUE_NOT_FOUND
+       }
+
+       return field.Interface(), nil
+}
+
+// Places the object into the session, a nil value will cause remove the key from the session
+// (or you can use the Session.Del(key) function
+func (s Session) Set(key string, value interface{}) error {
+       if value == nil {
+               s.Del(key)
+               return nil
+       }
+
+       s[key] = value
+       return nil
+}
+
+// Delete the key from the sessionObjects and Session
+func (s Session) Del(key string) {
+       sessionJsonMap := s.getSessionJsonMap()
+       delete(sessionJsonMap, key)
+       delete(s, key)
+}
+
+// Extracts the session as a map of [string keys] and json values
+func (s Session) getSessionJsonMap() map[string]string {
+       if sessionJson, found := s[SessionObjectKeyName]; found {
+               if _, valid := sessionJson.(map[string]string); !valid {
+                       sessionLog.Error("Session object key corrupted, reset", "was", sessionJson)
+                       s[SessionObjectKeyName] = map[string]string{}
+               }
+               // serialized data inside the session _objects
+       } else {
+               s[SessionObjectKeyName] = map[string]string{}
+       }
+
+       return s[SessionObjectKeyName].(map[string]string)
+}
+
+// Convert the map to a simple map[string]string map
+// this will marshal any non string objects encountered and store them the the jsonMap
+// The expiration time will also be assigned
+func (s Session) Serialize() map[string]string {
+       sessionJsonMap := s.getSessionJsonMap()
+       newMap := map[string]string{}
+       newObjectMap := map[string]string{}
+       for key, value := range sessionJsonMap {
+               newObjectMap[key] = value
+       }
+       for key, value := range s {
+               if key == SessionObjectKeyName || key == SessionMapKeyName {
+                       continue
+               }
+               if reflect.ValueOf(value).Kind() == reflect.String {
+                       newMap[key] = value.(string)
+                       continue
+               }
+               println("Serialize the data for", key)
+               if data, err := json.Marshal(value); err != nil {
+                       sessionLog.Error("Unable to marshal session ", "key", key, "error", err)
+                       continue
+               } else {
+                       newObjectMap[key] = string(data)
+               }
+       }
+       if len(newObjectMap) > 0 {
+               if data, err := json.Marshal(newObjectMap); err != nil {
+                       sessionLog.Error("Unable to marshal session ", "key", SessionObjectKeyName, "error", err)
+
+               } else {
+                       newMap[SessionObjectKeyName] = string(data)
+               }
+       }
+
+       return newMap
+}
+
+// Set the session object from the loaded data
+func (s Session) Load(data map[string]string) {
+       for key, value := range data {
+               if key == SessionObjectKeyName {
+                       target := map[string]string{}
+                       if err := json.Unmarshal([]byte(value), &target); err != nil {
+                               sessionLog.Error("Unable to unmarshal session ", "key", SessionObjectKeyName, "error", err)
+                       } else {
+                               s[key] = target
+                       }
+               } else {
+                       s[key] = value
+               }
+
+       }
+}
+
+// Checks to see if the session is empty
+func (s Session) Empty() bool {
+       i := 0
+       for k := range s {
+               i++
+               if k == SessionObjectKeyName || k == SessionMapKeyName {
+                       continue
+               }
+       }
+       return i == 0
+}
+
+func (s *Session) reflectValue(obj interface{}) reflect.Value {
+       var val reflect.Value
+
+       if reflect.TypeOf(obj).Kind() == reflect.Ptr {
+               val = reflect.ValueOf(obj).Elem()
+       } else {
+               val = reflect.ValueOf(obj)
+       }
+
+       return val
+}
+
+// Starting at position 1 drill into the object
+func (s Session) getNestedProperty(keys []string, newValue interface{}) (result interface{}, err error) {
+       for x := 1; x < len(keys); x++ {
+               newValue, err = s.GetProperty(keys[x], newValue)
+               if err != nil || newValue == nil {
+                       return newValue, err
+               }
+       }
+       return newValue, nil
+}
+
+// Always converts the data from the session mapped objects into the target,
+// it will store the results under the session key name SessionMapKeyName
+func (s Session) sessionDataFromMap(key string) (result interface{}, err error) {
+       var mapValue map[string]interface{}
+       uncastMapValue, found := s[SessionMapKeyName]
+       if !found {
+               mapValue = map[string]interface{}{}
+               s[SessionMapKeyName] = mapValue
+       } else if mapValue, found = uncastMapValue.(map[string]interface{}); !found {
+               // Unusual means that the value in the session was not expected
+               sessionLog.Errorf("Unusual means that the value in the session was not expected", "session", uncastMapValue)
+               mapValue = map[string]interface{}{}
+               s[SessionMapKeyName] = mapValue
+       }
+
+       // Try to extract the key from the map
+       result, found = mapValue[key]
+       if !found {
+               result, err = s.convertSessionData(key, nil)
+               if err == nil {
+                       mapValue[key] = result
+               }
+       }
+       return
+}
+
+// Unpack the object from the session map and store it in the session when done, if no error occurs
+func (s Session) sessionDataFromObject(key string, newValue interface{}) (result interface{}, err error) {
+       result, err = s.convertSessionData(key, newValue)
+       if err != nil {
+               return
+       }
+       s[key] = result
+       return
+}
+
+// Converts from the session json map into the target,
+func (s Session) convertSessionData(key string, target interface{}) (result interface{}, err error) {
+       sessionJsonMap := s.getSessionJsonMap()
+       v, found := sessionJsonMap[key]
+       if !found {
+               return target, SESSION_VALUE_NOT_FOUND
+       }
+
+       // Create a target if needed
+       if target == nil {
+               target = map[string]interface{}{}
+               if err := json.Unmarshal([]byte(v), &target); err != nil {
+                       return target, err
+               }
+       } else if err := json.Unmarshal([]byte(v), target); err != nil {
+               return target, err
+       }
+       result = target
+       return
+}