github.com/cloudwego/kitex@v0.9.0/tool/internal_pkg/pluginmode/thriftgo/hessian2.go (about) 1 // Copyright 2023 CloudWeGo Authors 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 implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package thriftgo 16 17 import ( 18 "io" 19 "io/ioutil" 20 "os" 21 "path/filepath" 22 "regexp" 23 "strings" 24 25 "github.com/cloudwego/thriftgo/config" 26 "gopkg.in/yaml.v3" 27 28 "github.com/cloudwego/kitex/tool/internal_pkg/generator" 29 "github.com/cloudwego/kitex/tool/internal_pkg/log" 30 "github.com/cloudwego/kitex/tool/internal_pkg/util" 31 ) 32 33 const ( 34 JavaExtensionOption = "java_extension" 35 36 DubboCodec = "github.com/kitex-contrib/codec-dubbo" 37 JavaThrift = "java.thrift" 38 JavaThriftAddress = "https://raw.githubusercontent.com/kitex-contrib/codec-dubbo/main/java/java.thrift" 39 ) 40 41 // Hessian2PreHook Hook before building cmd 42 func Hessian2PreHook(cfg *generator.Config) { 43 // add thrift option 44 cfg.ThriftOptions = append(cfg.ThriftOptions, "template=slim,with_reflection,code_ref") 45 cfg.ThriftOptions = append(cfg.ThriftOptions, "enable_nested_struct") 46 47 // run hessian2 options 48 for _, opt := range cfg.Hessian2Options { 49 runOption(cfg, opt) 50 } 51 } 52 53 func IsHessian2(a generator.Config) bool { 54 return strings.EqualFold(a.Protocol, "hessian2") 55 } 56 57 func EnableJavaExtension(a generator.Config) bool { 58 for _, v := range a.Hessian2Options { 59 if strings.EqualFold(v, JavaExtensionOption) { 60 return true 61 } 62 } 63 return false 64 } 65 66 // runOption Execute the corresponding function according to the option 67 func runOption(cfg *generator.Config, opt string) { 68 switch opt { 69 case JavaExtensionOption: 70 runJavaExtensionOption(cfg) 71 } 72 } 73 74 // runJavaExtensionOption Pull the extension file of java class from remote 75 func runJavaExtensionOption(cfg *generator.Config) { 76 // get java.thrift, we assume java.thrift and IDL in the same directory so that IDL just needs to include "java.thrift" 77 if path := util.JoinPath(filepath.Dir(cfg.IDL), JavaThrift); !util.Exists(path) { 78 if err := util.DownloadFile(JavaThriftAddress, path); err != nil { 79 log.Warn("Downloading java.thrift file failed:", err.Error()) 80 abs, err := filepath.Abs(path) 81 if err != nil { 82 abs = path 83 } 84 log.Warnf("You can try to download again. If the download still fails, you can choose to manually download \"%s\" to the local path \"%s\".", JavaThriftAddress, abs) 85 os.Exit(1) 86 } 87 } 88 89 // merge idl-ref configuration patch 90 patchIDLRefConfig(cfg) 91 } 92 93 // patchIDLRefConfig merge idl-ref configuration patch 94 func patchIDLRefConfig(cfg *generator.Config) { 95 // load the idl-ref config file (create it first if it does not exist) 96 // for making use of idl-ref of thriftgo, we need to check the project root directory 97 idlRef := filepath.Join(cfg.OutputPath, "idl-ref.yaml") 98 if !util.Exists(idlRef) { 99 idlRef = filepath.Join(cfg.OutputPath, "idl-ref.yml") 100 } 101 file, err := os.OpenFile(idlRef, os.O_RDWR|os.O_CREATE, 0o644) 102 if err != nil { 103 log.Warnf("Open %s file failed: %s\n", idlRef, err.Error()) 104 os.Exit(1) 105 } 106 defer file.Close() 107 idlRefCfg := loadIDLRefConfig(file.Name(), file) 108 109 // assume java.thrift and IDL are in the same directory 110 javaRef := filepath.Join(filepath.Dir(cfg.IDL), JavaThrift) 111 idlRefCfg.Ref[javaRef] = DubboCodec + "/java" 112 out, err := yaml.Marshal(idlRefCfg) 113 if err != nil { 114 log.Warn("Marshal configuration failed:", err.Error()) 115 os.Exit(1) 116 } 117 // clear the file content 118 if err := file.Truncate(0); err != nil { 119 log.Warnf("Truncate file %s failed: %s", idlRef, err.Error()) 120 os.Exit(1) 121 } 122 // set the file offset 123 if _, err := file.Seek(0, 0); err != nil { 124 log.Warnf("Seek file %s failed: %s", idlRef, err.Error()) 125 os.Exit(1) 126 } 127 _, err = file.Write(out) 128 if err != nil { 129 log.Warnf("Write to file %s failed: %s\n", idlRef, err.Error()) 130 os.Exit(1) 131 } 132 } 133 134 // loadIDLRefConfig load idl-ref config from file object 135 func loadIDLRefConfig(fileName string, reader io.Reader) *config.RawConfig { 136 data, err := ioutil.ReadAll(reader) 137 if err != nil { 138 log.Warnf("Read %s file failed: %s\n", fileName, err.Error()) 139 os.Exit(1) 140 } 141 142 // build idl ref config 143 idlRefCfg := new(config.RawConfig) 144 if len(data) == 0 { 145 idlRefCfg.Ref = make(map[string]interface{}) 146 } else { 147 err := yaml.Unmarshal(data, idlRefCfg) 148 if err != nil { 149 log.Warnf("Parse %s file failed: %s\n", fileName, err.Error()) 150 log.Warn("Please check whether the idl ref configuration is correct.") 151 os.Exit(2) 152 } 153 } 154 155 return idlRefCfg 156 } 157 158 var ( 159 javaObjectRe = regexp.MustCompile(`\*java\.Object\b`) 160 javaExceptionRe = regexp.MustCompile(`\*java\.Exception\b`) 161 javaExceptionEmptyVerificationRe = regexp.MustCompile(`return p\.Exception != nil\b`) 162 ) 163 164 // Hessian2PatchByReplace args is the arguments from command, subDirPath used for xx/xx/xx 165 func Hessian2PatchByReplace(args generator.Config, subDirPath string) error { 166 output := args.OutputPath 167 newPath := util.JoinPath(output, args.GenPath, subDirPath) 168 fs, err := ioutil.ReadDir(newPath) 169 if err != nil { 170 return err 171 } 172 173 for _, f := range fs { 174 fileName := util.JoinPath(args.OutputPath, args.GenPath, subDirPath, f.Name()) 175 if f.IsDir() { 176 subDirPath := util.JoinPath(subDirPath, f.Name()) 177 if err = Hessian2PatchByReplace(args, subDirPath); err != nil { 178 return err 179 } 180 } else if strings.HasSuffix(f.Name(), ".go") { 181 data, err := ioutil.ReadFile(fileName) 182 if err != nil { 183 return err 184 } 185 186 data = replaceJavaObject(data) 187 data = replaceJavaException(data) 188 data = replaceJavaExceptionEmptyVerification(data) 189 if err = ioutil.WriteFile(fileName, data, 0o644); err != nil { 190 return err 191 } 192 } 193 } 194 195 // users do not specify -service flag, we do not need to replace 196 if args.ServiceName == "" { 197 return nil 198 } 199 200 handlerName := util.JoinPath(output, "handler.go") 201 handler, err := ioutil.ReadFile(handlerName) 202 if err != nil { 203 return err 204 } 205 206 handler = replaceJavaObject(handler) 207 return ioutil.WriteFile(handlerName, handler, 0o644) 208 } 209 210 func replaceJavaObject(content []byte) []byte { 211 return javaObjectRe.ReplaceAll(content, []byte("java.Object")) 212 } 213 214 func replaceJavaException(content []byte) []byte { 215 return javaExceptionRe.ReplaceAll(content, []byte("java.Exception")) 216 } 217 218 // replaceJavaExceptionEmptyVerification is used to resolve this issue: 219 // After generating nested struct, the generated struct would be: 220 // 221 // type CustomizedException struct { 222 // *java.Exception 223 // } 224 // 225 // It has a method: 226 // func (p *EchoCustomizedException) IsSetException() bool { 227 // return p.Exception != nil 228 // } 229 // 230 // After invoking replaceJavaException, *java.Exception would be converted 231 // to java.Exception and IsSetException became invalid. We convert the statement 232 // to `return true` to ignore this problem. 233 func replaceJavaExceptionEmptyVerification(content []byte) []byte { 234 return javaExceptionEmptyVerificationRe.ReplaceAll(content, []byte("return true")) 235 }