github.com/altipla-consulting/ravendb-go-client@v0.1.3/raven_command.go (about) 1 package ravendb 2 3 import ( 4 "io" 5 "io/ioutil" 6 "net/http" 7 ) 8 9 var ( 10 _ RavenCommand = &RavenCommandBase{} 11 ) 12 13 // RavenCommand defines interface for server commands 14 type RavenCommand interface { 15 // those are meant to be over-written 16 createRequest(node *ServerNode) (*http.Request, error) 17 setResponse(response []byte, fromCache bool) error 18 setResponseRaw(response *http.Response, body io.Reader) error 19 20 send(client *http.Client, req *http.Request) (*http.Response, error) 21 22 // for all other functions, get access to underlying RavenCommandBase 23 getBase() *RavenCommandBase 24 } 25 26 type RavenCommandBase struct { 27 StatusCode int 28 ResponseType RavenCommandResponseType 29 CanCache bool 30 CanCacheAggressively bool 31 32 // if true, can be cached 33 IsReadRequest bool 34 35 FailedNodes map[*ServerNode]error 36 } 37 38 func NewRavenCommandBase() RavenCommandBase { 39 res := RavenCommandBase{ 40 ResponseType: RavenCommandResponseTypeObject, 41 CanCache: true, 42 CanCacheAggressively: true, 43 } 44 return res 45 } 46 47 func (c *RavenCommandBase) getBase() *RavenCommandBase { 48 return c 49 } 50 51 func (c *RavenCommandBase) setResponse(response []byte, fromCache bool) error { 52 if c.ResponseType == RavenCommandResponseTypeEmpty || c.ResponseType == RavenCommandResponseTypeRaw { 53 return throwInvalidResponse() 54 } 55 56 return newUnsupportedOperationError(c.ResponseType + " command must override the SetResponse method which expects response with the following type: " + c.ResponseType) 57 } 58 59 func (c *RavenCommandBase) setResponseRaw(response *http.Response, stream io.Reader) error { 60 panicIf(true, "When "+c.ResponseType+" is set to Raw then please override this method to handle the response. ") 61 return nil 62 } 63 64 func (c *RavenCommandBase) createRequest(node *ServerNode) (*http.Request, error) { 65 panicIf(true, "createRequest must be over-written by all types") 66 return nil, nil 67 } 68 69 func throwInvalidResponse() error { 70 return newIllegalStateError("Invalid response") 71 } 72 73 func (c *RavenCommandBase) send(client *http.Client, req *http.Request) (*http.Response, error) { 74 rsp, err := client.Do(req) 75 return rsp, err 76 } 77 78 func (c *RavenCommandBase) urlEncode(value string) string { 79 return urlEncode(value) 80 } 81 82 func ensureIsNotNullOrString(value string, name string) error { 83 if value == "" { 84 return newIllegalArgumentError("%s cannot be null or empty", name) 85 } 86 return nil 87 } 88 89 // Note: unused 90 func (c *RavenCommandBase) isFailedWithNode(node *ServerNode) bool { 91 if c.FailedNodes == nil { 92 return false 93 } 94 _, ok := c.FailedNodes[node] 95 return ok 96 } 97 98 // Note: in Java Raven.processResponse is virtual. 99 // That's impossible in Go, so we replace with stand-alone function that dispatches based on type 100 func ravenCommand_processResponse(cmd RavenCommand, cache *httpCache, response *http.Response, url string) (responseDisposeHandling, error) { 101 if cmdHead, ok := cmd.(*HeadDocumentCommand); ok { 102 return cmdHead.ProcessResponse(cache, response, url) 103 } 104 105 if cmdHead, ok := cmd.(*HeadAttachmentCommand); ok { 106 return cmdHead.processResponse(cache, response, url) 107 } 108 109 if cmdGet, ok := cmd.(*GetAttachmentCommand); ok { 110 return cmdGet.processResponse(cache, response, url) 111 } 112 113 if cmdQuery, ok := cmd.(*QueryStreamCommand); ok { 114 return cmdQuery.processResponse(cache, response, url) 115 } 116 117 if cmdStream, ok := cmd.(*StreamCommand); ok { 118 return cmdStream.processResponse(cache, response, url) 119 } 120 121 c := cmd.getBase() 122 123 if response.Body == nil { 124 return responseDisposeHandlingAutomatic, nil 125 } 126 127 statusCode := response.StatusCode 128 if c.ResponseType == RavenCommandResponseTypeEmpty || statusCode == http.StatusNoContent { 129 return responseDisposeHandlingAutomatic, nil 130 } 131 132 if c.ResponseType == RavenCommandResponseTypeObject { 133 contentLength := response.ContentLength 134 if contentLength == 0 { 135 return responseDisposeHandlingAutomatic, nil 136 } 137 138 // we intentionally don't dispose the reader here, we'll be using it 139 // in the command, any associated memory will be released on context reset 140 js, err := ioutil.ReadAll(response.Body) 141 if err != nil { 142 return responseDisposeHandlingAutomatic, err 143 } 144 145 if cache != nil { 146 c.cacheResponse(cache, url, response, js) 147 } 148 err = cmd.setResponse(js, false) 149 return responseDisposeHandlingAutomatic, err 150 } 151 152 err := cmd.setResponseRaw(response, response.Body) 153 return responseDisposeHandlingAutomatic, err 154 } 155 156 func (c *RavenCommandBase) cacheResponse(cache *httpCache, url string, response *http.Response, responseJson []byte) { 157 if !c.CanCache { 158 return 159 } 160 161 changeVector := gttpExtensionsGetEtagHeader(response) 162 if changeVector == nil { 163 return 164 } 165 166 cache.set(url, changeVector, responseJson) 167 } 168 169 // Note: unused 170 func (c *RavenCommandBase) addChangeVectorIfNotNull(changeVector *string, request *http.Request) { 171 if changeVector != nil { 172 request.Header.Add("If-Match", `"`+*changeVector+`"`) 173 } 174 } 175 176 func (c *RavenCommandBase) onResponseFailure(response *http.Response) { 177 // Note: it looks like it's meant to be virtual but there are no 178 // over-rides in Java code 179 } 180 181 // Note: hackish solution due to lack of generics 182 // Returns OperationIDReuslt for commands that have it as a result 183 // When new command returning OperationIDResult are added, we must extend it 184 func getCommandOperationIDResult(cmd RavenCommand) *OperationIDResult { 185 switch c := cmd.(type) { 186 case *CompactDatabaseCommand: 187 return c.Result 188 case *PatchByQueryCommand: 189 return c.Result 190 case *DeleteByIndexCommand: 191 return c.Result 192 } 193 194 panicIf(true, "called on a command %T that doesn't return OperationIDResult", cmd) 195 return nil 196 }