Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / emicklei / go-restful / curly.go
1 package restful
2
3 // Copyright 2013 Ernest Micklei. All rights reserved.
4 // Use of this source code is governed by a license
5 // that can be found in the LICENSE file.
6
7 import (
8         "net/http"
9         "regexp"
10         "sort"
11         "strings"
12 )
13
14 // CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
15 type CurlyRouter struct{}
16
17 // SelectRoute is part of the Router interface and returns the best match
18 // for the WebService and its Route for the given Request.
19 func (c CurlyRouter) SelectRoute(
20         webServices []*WebService,
21         httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
22
23         requestTokens := tokenizePath(httpRequest.URL.Path)
24
25         detectedService := c.detectWebService(requestTokens, webServices)
26         if detectedService == nil {
27                 if trace {
28                         traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
29                 }
30                 return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
31         }
32         candidateRoutes := c.selectRoutes(detectedService, requestTokens)
33         if len(candidateRoutes) == 0 {
34                 if trace {
35                         traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
36                 }
37                 return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
38         }
39         selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
40         if selectedRoute == nil {
41                 return detectedService, nil, err
42         }
43         return detectedService, selectedRoute, nil
44 }
45
46 // selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
47 func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
48         candidates := make(sortableCurlyRoutes, 0, 8)
49         for _, each := range ws.routes {
50                 matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens)
51                 if matches {
52                         candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
53                 }
54         }
55         sort.Sort(candidates)
56         return candidates
57 }
58
59 // matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
60 func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) {
61         if len(routeTokens) < len(requestTokens) {
62                 // proceed in matching only if last routeToken is wildcard
63                 count := len(routeTokens)
64                 if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
65                         return false, 0, 0
66                 }
67                 // proceed
68         }
69         for i, routeToken := range routeTokens {
70                 if i == len(requestTokens) {
71                         // reached end of request path
72                         return false, 0, 0
73                 }
74                 requestToken := requestTokens[i]
75                 if strings.HasPrefix(routeToken, "{") {
76                         paramCount++
77                         if colon := strings.Index(routeToken, ":"); colon != -1 {
78                                 // match by regex
79                                 matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
80                                 if !matchesToken {
81                                         return false, 0, 0
82                                 }
83                                 if matchesRemainder {
84                                         break
85                                 }
86                         }
87                 } else { // no { prefix
88                         if requestToken != routeToken {
89                                 return false, 0, 0
90                         }
91                         staticCount++
92                 }
93         }
94         return true, paramCount, staticCount
95 }
96
97 // regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
98 // format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
99 func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
100         regPart := routeToken[colon+1 : len(routeToken)-1]
101         if regPart == "*" {
102                 if trace {
103                         traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
104                 }
105                 return true, true
106         }
107         matched, err := regexp.MatchString(regPart, requestToken)
108         return (matched && err == nil), false
109 }
110
111 var jsr311Router = RouterJSR311{}
112
113 // detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
114 // headers of the Request. See also RouterJSR311 in jsr311.go
115 func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
116         // tracing is done inside detectRoute
117         return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
118 }
119
120 // detectWebService returns the best matching webService given the list of path tokens.
121 // see also computeWebserviceScore
122 func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
123         var best *WebService
124         score := -1
125         for _, each := range webServices {
126                 matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
127                 if matches && (eachScore > score) {
128                         best = each
129                         score = eachScore
130                 }
131         }
132         return best
133 }
134
135 // computeWebserviceScore returns whether tokens match and
136 // the weighted score of the longest matching consecutive tokens from the beginning.
137 func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
138         if len(tokens) > len(requestTokens) {
139                 return false, 0
140         }
141         score := 0
142         for i := 0; i < len(tokens); i++ {
143                 each := requestTokens[i]
144                 other := tokens[i]
145                 if len(each) == 0 && len(other) == 0 {
146                         score++
147                         continue
148                 }
149                 if len(other) > 0 && strings.HasPrefix(other, "{") {
150                         // no empty match
151                         if len(each) == 0 {
152                                 return false, score
153                         }
154                         score += 1
155                 } else {
156                         // not a parameter
157                         if each != other {
158                                 return false, score
159                         }
160                         score += (len(tokens) - i) * 10 //fuzzy
161                 }
162         }
163         return true, score
164 }