Merge pull request #51 from 30x/accesstoken-endpoint

Add accesstoken endpoint for gateway to use in microservice requests
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..233c318
--- /dev/null
+++ b/api.go
@@ -0,0 +1,62 @@
+package apidApigeeSync
+
+import (
+	"encoding/json"
+	"github.com/30x/apid-core"
+	"net/http"
+	"strconv"
+	"time"
+)
+
+const tokenEndpoint = "/accesstoken"
+
+func InitAPI(services apid.Services) {
+	services.API().HandleFunc(tokenEndpoint, getAccessToken).Methods("GET")
+}
+
+func getAccessToken(w http.ResponseWriter, r *http.Request) {
+	b := r.URL.Query().Get("block")
+	var timeout int
+	if b != "" {
+		var err error
+		timeout, err = strconv.Atoi(b)
+		if err != nil {
+			writeError(w, http.StatusBadRequest, "bad block value, must be number of seconds")
+			return
+		}
+	}
+	log.Debugf("api timeout: %d", timeout)
+	ifNoneMatch := r.Header.Get("If-None-Match")
+
+	if apidTokenManager.getBearerToken() != ifNoneMatch {
+		w.Write([]byte(apidTokenManager.getBearerToken()))
+		return
+	}
+
+	select {
+	case <-apidTokenManager.getTokenReadyChannel():
+		w.Write([]byte(apidTokenManager.getBearerToken()))
+	case <-time.After(time.Duration(timeout) * time.Second):
+		w.WriteHeader(http.StatusNotModified)
+	}
+}
+
+func writeError(w http.ResponseWriter, status int, reason string) {
+	w.WriteHeader(status)
+	e := errorResponse{
+		ErrorCode: status,
+		Reason:    reason,
+	}
+	bytes, err := json.Marshal(e)
+	if err != nil {
+		log.Errorf("unable to marshal errorResponse: %v", err)
+	} else {
+		w.Write(bytes)
+	}
+	log.Debugf("sending %d error to client: %s", status, reason)
+}
+
+type errorResponse struct {
+	ErrorCode int    `json:"errorCode"`
+	Reason    string `json:"reason"`
+}
diff --git a/change_test.go b/change_test.go
index 383bff6..67331c4 100644
--- a/change_test.go
+++ b/change_test.go
@@ -155,6 +155,10 @@
 	invalidateChan chan bool
 }
 
+func (t *dummyTokenManager) getTokenReadyChannel() <-chan bool {
+	return nil
+}
+
 func (t *dummyTokenManager) getBearerToken() string {
 	return ""
 }
diff --git a/init.go b/init.go
index 245d4af..1ad67fd 100644
--- a/init.go
+++ b/init.go
@@ -189,6 +189,7 @@
 	 */
 	events.ListenOnceFunc(apid.SystemEventsSelector, postInitPlugins)
 
+	InitAPI(services)
 	log.Debug("end init")
 
 	return pluginData, nil
diff --git a/managerInterfaces.go b/managerInterfaces.go
index 5022bdd..8ef331c 100644
--- a/managerInterfaces.go
+++ b/managerInterfaces.go
@@ -26,6 +26,7 @@
 	close()
 	getRetrieveNewTokenClosure(*url.URL) func(chan bool) error
 	start()
+	getTokenReadyChannel() <-chan bool
 }
 
 type snapShotManager interface {
diff --git a/token.go b/token.go
index 1612025..46e52cd 100644
--- a/token.go
+++ b/token.go
@@ -49,6 +49,7 @@
 		invalidateTokenChan: make(chan bool),
 		returnTokenChan:     make(chan *OauthToken),
 		invalidateDone:      make(chan bool),
+		tokenUpdatedChan:    make(chan bool, 1),
 		isClosed:            &isClosedInt,
 	}
 	return t
@@ -64,6 +65,7 @@
 	refreshTimer        <-chan time.Time
 	returnTokenChan     chan *OauthToken
 	invalidateDone      chan bool
+	tokenUpdatedChan    chan bool
 }
 
 func (t *simpleTokenManager) start() {
@@ -218,10 +220,21 @@
 		t.token = &token
 		config.Set(configBearerToken, token.AccessToken)
 
+		//don't block on the buffered channel.  that means there is already a signal to serve new token
+		select {
+		case t.tokenUpdatedChan <- true:
+		default:
+			log.Debug("Token refresh notification already sent")
+		}
+
 		return nil
 	}
 }
 
+func (t *simpleTokenManager) getTokenReadyChannel() <-chan bool {
+	return t.tokenUpdatedChan
+}
+
 type OauthToken struct {
 	IssuedAt    int64    `json:"issuedAt"`
 	AppName     string   `json:"applicationName"`