github.com/aneshas/cli@v0.0.0-20180104210444-aec958fa47db/langs/java.go (about) 1 package langs 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "os" 12 "path/filepath" 13 "strings" 14 ) 15 16 // JavaLangHelper provides a set of helper methods for the lifecycle of Java Maven projects 17 type JavaLangHelper struct { 18 BaseHelper 19 version string 20 latestFdkVersion string 21 } 22 23 func (h *JavaLangHelper) Handles(lang string) bool { 24 for _, s := range h.LangStrings() { 25 if lang == s { 26 return true 27 } 28 } 29 return false 30 } 31 func (h *JavaLangHelper) Runtime() string { 32 return h.LangStrings()[0] 33 } 34 35 // TOOD: same as python, I think we should just have version tags on the single runtime, eg: `java:8` or `java:9` 36 func (lh *JavaLangHelper) LangStrings() []string { 37 if lh.version == "1.8" { 38 return []string{"java8"} 39 } 40 return []string{"java9", "java"} 41 42 } 43 func (lh *JavaLangHelper) Extensions() []string { 44 return []string{".java"} 45 } 46 47 // BuildFromImage returns the Docker image used to compile the Maven function project 48 func (lh *JavaLangHelper) BuildFromImage() (string, error) { 49 50 fdkVersion, err := lh.getFDKAPIVersion() 51 if err != nil { 52 return "", err 53 } 54 55 if lh.version == "1.8" { 56 return fmt.Sprintf("fnproject/fn-java-fdk-build:%s", fdkVersion), nil 57 } else if lh.version == "9" { 58 return fmt.Sprintf("fnproject/fn-java-fdk-build:jdk9-%s", fdkVersion), nil 59 } else { 60 return "", fmt.Errorf("unsupported java version %s", lh.version) 61 } 62 } 63 64 // RunFromImage returns the Docker image used to run the Java function. 65 func (lh *JavaLangHelper) RunFromImage() (string, error) { 66 fdkVersion, err := lh.getFDKAPIVersion() 67 if err != nil { 68 return "", err 69 } 70 if lh.version == "1.8" { 71 return fmt.Sprintf("fnproject/fn-java-fdk:%s", fdkVersion), nil 72 } else if lh.version == "9" { 73 return fmt.Sprintf("fnproject/fn-java-fdk:jdk9-%s", fdkVersion), nil 74 } else { 75 return "", fmt.Errorf("unsupported java version %s", lh.version) 76 } 77 } 78 79 // HasBoilerplate returns whether the Java runtime has boilerplate that can be generated. 80 func (lh *JavaLangHelper) HasBoilerplate() bool { return true } 81 82 // Java defaults to http 83 func (lh *JavaLangHelper) DefaultFormat() string { return "http" } 84 85 // GenerateBoilerplate will generate function boilerplate for a Java runtime. The default boilerplate is for a Maven 86 // project. 87 func (lh *JavaLangHelper) GenerateBoilerplate() error { 88 wd, err := os.Getwd() 89 if err != nil { 90 return err 91 } 92 93 pathToPomFile := filepath.Join(wd, "pom.xml") 94 if exists(pathToPomFile) { 95 return ErrBoilerplateExists 96 } 97 98 apiVersion, err := lh.getFDKAPIVersion() 99 if err != nil { 100 return err 101 } 102 103 if err := ioutil.WriteFile(pathToPomFile, []byte(pomFileContent(apiVersion, lh.version)), os.FileMode(0644)); err != nil { 104 return err 105 } 106 107 mkDirAndWriteFile := func(dir, filename, content string) error { 108 fullPath := filepath.Join(wd, dir) 109 if err = os.MkdirAll(fullPath, os.FileMode(0755)); err != nil { 110 return err 111 } 112 113 fullFilePath := filepath.Join(fullPath, filename) 114 return ioutil.WriteFile(fullFilePath, []byte(content), os.FileMode(0644)) 115 } 116 117 err = mkDirAndWriteFile("src/main/java/com/example/fn", "HelloFunction.java", helloJavaSrcBoilerplate) 118 if err != nil { 119 return err 120 } 121 122 return mkDirAndWriteFile("src/test/java/com/example/fn", "HelloFunctionTest.java", helloJavaTestBoilerplate) 123 } 124 125 // Cmd returns the Java runtime Docker entrypoint that will be executed when the function is executed. 126 func (lh *JavaLangHelper) Cmd() (string, error) { 127 return "com.example.fn.HelloFunction::handleRequest", nil 128 } 129 130 // DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function jar and dependencies. 131 func (lh *JavaLangHelper) DockerfileCopyCmds() []string { 132 return []string{ 133 "COPY --from=build-stage /function/target/*.jar /function/app/", 134 } 135 } 136 137 // DockerfileBuildCmds returns the build stage steps to compile the Maven function project. 138 func (lh *JavaLangHelper) DockerfileBuildCmds() []string { 139 return []string{ 140 fmt.Sprintf("ENV MAVEN_OPTS %s", mavenOpts()), 141 "ADD pom.xml /function/pom.xml", 142 "RUN [\"mvn\", \"package\", \"dependency:copy-dependencies\", \"-DincludeScope=runtime\", " + 143 "\"-DskipTests=true\", \"-Dmdep.prependGroupId=true\", \"-DoutputDirectory=target\", \"--fail-never\"]", 144 "ADD src /function/src", 145 "RUN [\"mvn\", \"package\"]", 146 } 147 } 148 149 // HasPreBuild returns whether the Java Maven runtime has a pre-build step. 150 func (lh *JavaLangHelper) HasPreBuild() bool { return true } 151 152 // PreBuild ensures that the expected the function is based is a maven project. 153 func (lh *JavaLangHelper) PreBuild() error { 154 wd, err := os.Getwd() 155 if err != nil { 156 return err 157 } 158 159 if !exists(filepath.Join(wd, "pom.xml")) { 160 return errors.New("Could not find pom.xml - are you sure this is a Maven project?") 161 } 162 163 return nil 164 } 165 166 func mavenOpts() string { 167 var opts bytes.Buffer 168 169 if parsedURL, err := url.Parse(os.Getenv("http_proxy")); err == nil { 170 opts.WriteString(fmt.Sprintf("-Dhttp.proxyHost=%s ", parsedURL.Hostname())) 171 opts.WriteString(fmt.Sprintf("-Dhttp.proxyPort=%s ", parsedURL.Port())) 172 } 173 174 if parsedURL, err := url.Parse(os.Getenv("https_proxy")); err == nil { 175 opts.WriteString(fmt.Sprintf("-Dhttps.proxyHost=%s ", parsedURL.Hostname())) 176 opts.WriteString(fmt.Sprintf("-Dhttps.proxyPort=%s ", parsedURL.Port())) 177 } 178 179 nonProxyHost := os.Getenv("no_proxy") 180 opts.WriteString(fmt.Sprintf("-Dhttp.nonProxyHosts=%s ", strings.Replace(nonProxyHost, ",", "|", -1))) 181 182 opts.WriteString("-Dmaven.repo.local=/usr/share/maven/ref/repository") 183 184 return opts.String() 185 } 186 187 /* TODO temporarily generate maven project boilerplate from hardcoded values. 188 Will eventually move to using a maven archetype. 189 */ 190 func pomFileContent(APIversion, javaVersion string) string { 191 return fmt.Sprintf(pomFile, APIversion, javaVersion, javaVersion) 192 } 193 194 func (lh *JavaLangHelper) getFDKAPIVersion() (string, error) { 195 196 if lh.latestFdkVersion != "" { 197 return lh.latestFdkVersion, nil 198 } 199 200 const versionURL = "https://api.bintray.com/search/packages/maven?repo=fnproject&g=com.fnproject.fn&a=fdk" 201 const versionEnv = "FN_JAVA_FDK_VERSION" 202 fetchError := fmt.Errorf("Failed to fetch latest Java FDK javaVersion from %v. Check your network settings or manually override the javaVersion by setting %s", versionURL, versionEnv) 203 204 type parsedResponse struct { 205 Version string `json:"latest_version"` 206 } 207 version := os.Getenv(versionEnv) 208 if version != "" { 209 return version, nil 210 } 211 resp, err := http.Get(versionURL) 212 if err != nil || resp.StatusCode != 200 { 213 return "", fetchError 214 } 215 216 buf := bytes.Buffer{} 217 _, err = buf.ReadFrom(resp.Body) 218 if err != nil { 219 return "", fetchError 220 } 221 222 parsedResp := make([]parsedResponse, 1) 223 err = json.Unmarshal(buf.Bytes(), &parsedResp) 224 if err != nil { 225 return "", fetchError 226 } 227 228 version = parsedResp[0].Version 229 lh.latestFdkVersion = version 230 return version, nil 231 } 232 233 func (lh *JavaLangHelper) FixImagesOnInit() bool { 234 return true 235 } 236 237 const ( 238 pomFile = `<?xml version="1.0" encoding="UTF-8"?> 239 <project xmlns="http://maven.apache.org/POM/4.0.0" 240 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 241 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 242 <modelVersion>4.0.0</modelVersion> 243 <properties> 244 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 245 <fdk.version>%s</fdk.version> 246 </properties> 247 <groupId>com.example.fn</groupId> 248 <artifactId>hello</artifactId> 249 <version>1.0.0</version> 250 251 <repositories> 252 <repository> 253 <id>fn-release-repo</id> 254 <url>https://dl.bintray.com/fnproject/fnproject</url> 255 <releases> 256 <enabled>true</enabled> 257 </releases> 258 <snapshots> 259 <enabled>false</enabled> 260 </snapshots> 261 </repository> 262 </repositories> 263 264 <dependencies> 265 <dependency> 266 <groupId>com.fnproject.fn</groupId> 267 <artifactId>api</artifactId> 268 <version>${fdk.version}</version> 269 </dependency> 270 <dependency> 271 <groupId>com.fnproject.fn</groupId> 272 <artifactId>testing</artifactId> 273 <version>${fdk.version}</version> 274 <scope>test</scope> 275 </dependency> 276 <dependency> 277 <groupId>junit</groupId> 278 <artifactId>junit</artifactId> 279 <version>4.12</version> 280 <scope>test</scope> 281 </dependency> 282 </dependencies> 283 284 <build> 285 <plugins> 286 <plugin> 287 <groupId>org.apache.maven.plugins</groupId> 288 <artifactId>maven-compiler-plugin</artifactId> 289 <version>3.3</version> 290 <configuration> 291 <source>%s</source> 292 <target>%s</target> 293 </configuration> 294 </plugin> 295 </plugins> 296 </build> 297 </project> 298 ` 299 300 helloJavaSrcBoilerplate = `package com.example.fn; 301 302 public class HelloFunction { 303 304 public String handleRequest(String input) { 305 String name = (input == null || input.isEmpty()) ? "world" : input; 306 307 return "Hello, " + name + "!"; 308 } 309 310 }` 311 312 helloJavaTestBoilerplate = `package com.example.fn; 313 314 import com.fnproject.fn.testing.*; 315 import org.junit.*; 316 317 import static org.junit.Assert.*; 318 319 public class HelloFunctionTest { 320 321 @Rule 322 public final FnTestingRule testing = FnTestingRule.createDefault(); 323 324 @Test 325 public void shouldReturnGreeting() { 326 testing.givenEvent().enqueue(); 327 testing.thenRun(HelloFunction.class, "handleRequest"); 328 329 FnResult result = testing.getOnlyResult(); 330 assertEquals("Hello, world!", result.getBodyAsString()); 331 } 332 333 }` 334 )