From 04e4b6e78c5b941cf0d87f34becdaf644857b046 Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 4 Nov 2022 17:41:12 +0900 Subject: [PATCH 1/4] Added .gitignore from gitignore for Eclipse. #2 --- rest/java/.gitignore | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 rest/java/.gitignore diff --git a/rest/java/.gitignore b/rest/java/.gitignore new file mode 100644 index 0000000..acec74a --- /dev/null +++ b/rest/java/.gitignore @@ -0,0 +1,60 @@ +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project From 1caa2a459cb4588a2c4578cc9fa349fb291c4863 Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 4 Nov 2022 17:45:41 +0900 Subject: [PATCH 2/4] Added Java source files. #2 --- .../main/java/io/werender/examples/App.java | 91 +++++++++++++++ .../java/io/werender/examples/Command.java | 7 ++ .../io/werender/examples/CommandContext.java | 24 ++++ .../java/io/werender/examples/Config.java | 109 ++++++++++++++++++ .../java/io/werender/examples/FileUtil.java | 59 ++++++++++ .../examples/SignatureInterceptor.java | 67 +++++++++++ .../werender/examples/UploadFileCommand.java | 82 +++++++++++++ 7 files changed, 439 insertions(+) create mode 100644 rest/java/src/main/java/io/werender/examples/App.java create mode 100644 rest/java/src/main/java/io/werender/examples/Command.java create mode 100644 rest/java/src/main/java/io/werender/examples/CommandContext.java create mode 100644 rest/java/src/main/java/io/werender/examples/Config.java create mode 100644 rest/java/src/main/java/io/werender/examples/FileUtil.java create mode 100644 rest/java/src/main/java/io/werender/examples/SignatureInterceptor.java create mode 100644 rest/java/src/main/java/io/werender/examples/UploadFileCommand.java diff --git a/rest/java/src/main/java/io/werender/examples/App.java b/rest/java/src/main/java/io/werender/examples/App.java new file mode 100644 index 0000000..2cd8ce2 --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/App.java @@ -0,0 +1,91 @@ +package io.werender.examples; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openapitools.client.ApiClient; +import org.openapitools.client.Configuration; +import org.openapitools.client.model.StorageFile; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; + +public class App { + private static Log log = LogFactory.getLog(App.class); + + private static final String APP_NAME = "werender-example-rest-java"; + + private static final String COMMAND_UPLOAD_FILE = "upload_file"; + private static final String COMMAND_HELP = "help"; + + public static void handleUploadFile(CommandLine cmdLine) { + // Retrieve configuration. + Config config = Config.create(); + log.info(String.format("Using server %s", config.getAddress())); + + // Create API client. + ApiClient apiClient = Configuration.getDefaultApiClient(); + apiClient.setBasePath(config.getAddress()); + apiClient.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15.7) WeRender/0.4.0"); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new SignatureInterceptor(config.getPublicKey(), config.getPrivateKey())) + .addInterceptor(new HttpLoggingInterceptor()).build(); + apiClient.setHttpClient(client); + + // Upload the local file to remote directory. + try { + // Get local file path and remote directory path. + String[] optionValues = cmdLine.getOptionValues(COMMAND_UPLOAD_FILE); + String localFilePath = optionValues[0]; + String remoteDirPath = optionValues[1]; + log.info(String.format("Start to upload file from %s to %s", localFilePath, remoteDirPath)); + + // Create command to do the actual work. + CommandContext commandContext = new CommandContext(apiClient); + UploadFileCommand command = new UploadFileCommand(localFilePath, remoteDirPath); + StorageFile file = command.execute(commandContext); + + log.info(String.format("Uploaded file from %s to %s with id %s", localFilePath, remoteDirPath, + file.getId())); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void handleHelp(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(APP_NAME, options); + } + + public static void main(String[] args) { + // Prepare command line options. + Option uploadFileOption = Option.builder(COMMAND_UPLOAD_FILE).hasArgs() + .desc("upload a file from local path to remote directory").build(); + Option helpOption = Option.builder(COMMAND_HELP).desc("print help").build(); + + Options options = new Options(); + options.addOption(uploadFileOption); + options.addOption(helpOption); + + // Process command line. + try { + CommandLineParser parser = new DefaultParser(); + CommandLine cmdLine = parser.parse(options, args); + if (cmdLine.hasOption(COMMAND_HELP)) { + handleHelp(options); + } else if (cmdLine.hasOption(COMMAND_UPLOAD_FILE)) { + handleUploadFile(cmdLine); + } else { + handleHelp(options); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/rest/java/src/main/java/io/werender/examples/Command.java b/rest/java/src/main/java/io/werender/examples/Command.java new file mode 100644 index 0000000..164fd8b --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/Command.java @@ -0,0 +1,7 @@ +package io.werender.examples; + +public interface Command { + + public T execute(CommandContext context) throws Exception; + +} diff --git a/rest/java/src/main/java/io/werender/examples/CommandContext.java b/rest/java/src/main/java/io/werender/examples/CommandContext.java new file mode 100644 index 0000000..0db0d46 --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/CommandContext.java @@ -0,0 +1,24 @@ +package io.werender.examples; + +import org.openapitools.client.ApiClient; +import org.openapitools.client.api.StorageApi; + +public class CommandContext { + + private ApiClient apiClient; + private StorageApi storageApi; + + public CommandContext(ApiClient apiClient) { + this.apiClient = apiClient; + storageApi = new StorageApi(apiClient); + } + + public ApiClient getApiClient() { + return apiClient; + } + + public StorageApi getStorageApi() { + return storageApi; + } + +} diff --git a/rest/java/src/main/java/io/werender/examples/Config.java b/rest/java/src/main/java/io/werender/examples/Config.java new file mode 100644 index 0000000..db85185 --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/Config.java @@ -0,0 +1,109 @@ +package io.werender.examples; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.apache.commons.lang3.SystemUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class Config { + private static Log log = LogFactory.getLog(Config.class); + + public static final String DEFAULT_SERVER_ADDRESS = "https://api-us-east-2.werender.io/v1"; + public static final String DEFAULT_SERVER_VERSION = "v1"; + + private String address; + private String publicKey; + private String privateKey; + + public static Config create() { + /* Create instance. */ + Config config = new Config(); + + /* Get home folder and configuration folder. */ + String homeDirPath = SystemUtils.getUserHome().getAbsolutePath(); + String configDirPath = new String(homeDirPath); + if (SystemUtils.IS_OS_MAC_OSX) { + configDirPath += "/Library/Application Support/werender"; + } else { + log.fatal(String.format("Unsupported platform")); + } + + /* Read werender.config file. */ + try { + String serverAddress = Config.DEFAULT_SERVER_ADDRESS; + + String configFilePath = configDirPath + "/werender.config"; + FileReader reader = new FileReader(configFilePath); + JsonParser parser = new JsonParser(); + JsonElement rootElement = parser.parse(reader); + JsonObject rootObject = rootElement.getAsJsonObject(); + if (rootObject.has("internal")) { + JsonObject internalObject = rootObject.get("internal").getAsJsonObject(); + if (internalObject.has("server")) { + JsonObject serverObject = internalObject.get("server").getAsJsonObject(); + if (serverObject.has("server_address")) { + serverAddress = serverObject.get("server_address").getAsString(); + } + } + } + + if (!serverAddress.endsWith(DEFAULT_SERVER_VERSION)) { + serverAddress += String.format("/%s", DEFAULT_SERVER_VERSION); + } + config.setAddress(serverAddress); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + /* Read werender.key file. */ + try { + String keyFilePath = configDirPath + "/werender.key"; + String key = Files.readString(Paths.get(keyFilePath)); + String[] keyTokens = key.split(","); + config.setPublicKey(keyTokens[0]); + config.setPrivateKey(keyTokens[1].trim()); + } catch (IOException e) { + e.printStackTrace(); + } + + /* Return instance. */ + return config; + } + + private Config() { + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + +} diff --git a/rest/java/src/main/java/io/werender/examples/FileUtil.java b/rest/java/src/main/java/io/werender/examples/FileUtil.java new file mode 100644 index 0000000..d72f1ac --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/FileUtil.java @@ -0,0 +1,59 @@ +package io.werender.examples; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.MessageDigest; + +import javax.xml.bind.annotation.adapters.HexBinaryAdapter; + +public final class FileUtil { + + public static final String MODE_STRICT = "strict"; + public static final String MODE_UNIQUE = "unique"; + + public static final String EXT_FBX = "fbx"; + public static final String EXT_USD = "usd"; + + public static final String CONTENT_TYPE_BINARY = "application/octet-stream"; + public static final String CONTENT_TYPE_MODEL_FBX = "model/fbx"; + public static final String CONTENT_TYPE_MODEL_USD = "model/usd"; + + public static String computeFileChecksum(File file) throws Exception { + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + try (InputStream input = new FileInputStream(file)) { + + byte[] buffer = new byte[8192]; + int len = input.read(buffer); + + while (len != -1) { + sha1.update(buffer, 0, len); + len = input.read(buffer); + } + + String checksum = new HexBinaryAdapter().marshal(sha1.digest()); + checksum = "sha1:" + checksum.toLowerCase(); + return checksum; + } + } + + public static String getContentType(String filePath) { + String contentType = CONTENT_TYPE_BINARY; + String[] tokens = filePath.split("\\."); + if (tokens.length > 0) { + String lastToken = tokens[tokens.length - 1]; + switch (lastToken) { + case EXT_FBX: + contentType = CONTENT_TYPE_MODEL_FBX; + break; + case EXT_USD: + contentType = CONTENT_TYPE_MODEL_USD; + break; + default: + break; + } + } + return contentType; + } + +} diff --git a/rest/java/src/main/java/io/werender/examples/SignatureInterceptor.java b/rest/java/src/main/java/io/werender/examples/SignatureInterceptor.java new file mode 100644 index 0000000..b034976 --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/SignatureInterceptor.java @@ -0,0 +1,67 @@ +package io.werender.examples; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class SignatureInterceptor implements Interceptor { + private static final String SIGNATURE_ALGORITHM = "HmacSHA1"; + + private String publicKey; + private String privateKey; + + private static String getDate() { + Calendar calendar = Calendar.getInstance(); + SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + return dateFormat.format(calendar.getTime()); + } + + private static String getSignature(String method, String host, String date, String privateKey) { + String message = String.format("%s\n%s\n%s", method, host, date); + String signature = null; + try { + SecretKeySpec signingKey = new SecretKeySpec(privateKey.getBytes(), SIGNATURE_ALGORITHM); + Mac mac = Mac.getInstance(SIGNATURE_ALGORITHM); + mac.init(signingKey); + byte[] macOutput = mac.doFinal(message.getBytes()); + signature = Base64.getEncoder().encodeToString(macOutput); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } + return signature; + } + + public SignatureInterceptor(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + String method = request.method(); + String date = getDate(); + String host = request.url().host(); + String signature = getSignature(method, host, date, privateKey); + Request newRequest = request.newBuilder() + .addHeader("Authorization", String.format("WR %s:%s", publicKey, signature)) + .addHeader("Date", getDate()).addHeader("Host", host).build(); + return chain.proceed(newRequest); + } + +} diff --git a/rest/java/src/main/java/io/werender/examples/UploadFileCommand.java b/rest/java/src/main/java/io/werender/examples/UploadFileCommand.java new file mode 100644 index 0000000..dab72a4 --- /dev/null +++ b/rest/java/src/main/java/io/werender/examples/UploadFileCommand.java @@ -0,0 +1,82 @@ +package io.werender.examples; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +import org.openapitools.client.model.StorageChunk; +import org.openapitools.client.model.StorageFile; +import org.openapitools.client.model.StorageFileCreate200Response; +import org.openapitools.client.model.StorageFileCreateRequest; +import org.openapitools.client.model.StorageFileFinalizeRequest; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class UploadFileCommand implements Command { + + private String localFilePath; + private String remoteDirPath; + + public UploadFileCommand(String localFilePath, String remoteDirPath) { + this.localFilePath = localFilePath; + this.remoteDirPath = remoteDirPath; + } + + @Override + public StorageFile execute(CommandContext context) throws Exception { + // Calculate file checksum. + File localFile = Paths.get(localFilePath).toFile(); + String localFileChecksum = FileUtil.computeFileChecksum(localFile); + String localFileContentType = FileUtil.getContentType(localFilePath); + + String localFileName = Paths.get(localFilePath).getFileName().toString(); + String remoteFilePath = Paths.get(remoteDirPath, localFileName).toString(); + + // Create file. + StorageFileCreateRequest createReq = new StorageFileCreateRequest(); + createReq.setChecksum(localFileChecksum); + createReq.setContentType(localFileContentType); + createReq.setMode(FileUtil.MODE_UNIQUE); + createReq.setPath(remoteFilePath); + createReq.setSize(Files.size(Paths.get(localFilePath))); + StorageFileCreate200Response createRes = context.getStorageApi().storageFileCreate(createReq); + + // Get file. + StorageFileFinalizeRequest getReq = new StorageFileFinalizeRequest(); + getReq.setId(createRes.getId()); + StorageFile file = context.getStorageApi().storageFileGet(null, getReq); + + // Upload file if there is no data. + long fileFinalizedAt = file.getFinalizedAt(); + if (fileFinalizedAt == 0) { + InputStream fileStream = new FileInputStream(Paths.get(localFilePath).toFile()); + + OkHttpClient httpClient = new OkHttpClient(); + List chunks = createRes.getChunks(); + for (StorageChunk chunk : chunks) { + Long chunkSize = chunk.getSize(); + byte[] chunkData = fileStream.readNBytes(chunkSize.intValue()); + String chunkUrl = chunk.getUrl(); + Request chunkReq = new Request.Builder().url(chunkUrl) + .addHeader("Content-Type", FileUtil.CONTENT_TYPE_BINARY).put(RequestBody.create(chunkData)) + .build(); + Response chunkRes = httpClient.newCall(chunkReq).execute(); + if (!chunkRes.isSuccessful()) { + return null; + } + } + + // Finalize file. + file = context.getStorageApi().storageFileFinalize(getReq); + } + + // Return file. + return file; + } +} From 790dc54f797db637fed43e7cf6150c3b947e618c Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 4 Nov 2022 17:47:41 +0900 Subject: [PATCH 3/4] Added Maven pom.xml file. #2 --- rest/java/pom.xml | 181 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 rest/java/pom.xml diff --git a/rest/java/pom.xml b/rest/java/pom.xml new file mode 100644 index 0000000..dec2bb9 --- /dev/null +++ b/rest/java/pom.xml @@ -0,0 +1,181 @@ + + + + 4.0.0 + + io.werender.examples + werender-examples-rest-java + 0.1.0 + + werender-examples-rest-java + https://github.com/j-cube/werender-examples + + + UTF-8 + 1.7 + 1.7 + http://192.168.0.105:50000/v1/api + + + + + commons-cli + commons-cli + 1.5.0 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + javax.ws.rs + javax.ws.rs-api + 2.1.1 + + + junit + junit + 4.11 + test + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + com.squareup.okhttp3 + logging-interceptor + 4.10.0 + + + com.google.code.gson + gson + 2.9.1 + + + io.gsonfire + gson-fire + 1.8.5 + + + org.openapitools + openapi-generator + 6.2.1 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + io.werender.examples.App + + + + + + + + com.googlecode.maven-download-plugin + download-maven-plugin + 1.6.8 + + + install-whatever + test + + wget + + + + + ${openapi.spec.url} + ${project.basedir}/src/main/resources/openapi + api.json + + + + org.openapitools + openapi-generator-maven-plugin + 6.2.1 + + + + generate + + + ${project.basedir}/src/main/resources/openapi/api.json + java + + false + ${project.basedir}/src/gen/java/main + + + + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + From 6994abbf3b6cd79d5c4e21a04fb70249d7731413 Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 4 Nov 2022 17:57:11 +0900 Subject: [PATCH 4/4] Search werender.config for Linux and Windows. #2 --- .../main/java/io/werender/examples/Config.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/rest/java/src/main/java/io/werender/examples/Config.java b/rest/java/src/main/java/io/werender/examples/Config.java index db85185..269a204 100644 --- a/rest/java/src/main/java/io/werender/examples/Config.java +++ b/rest/java/src/main/java/io/werender/examples/Config.java @@ -25,19 +25,23 @@ public class Config { private String privateKey; public static Config create() { - /* Create instance. */ + // Create instance. Config config = new Config(); - /* Get home folder and configuration folder. */ + // Get configuration folder where contains werender.config file. String homeDirPath = SystemUtils.getUserHome().getAbsolutePath(); String configDirPath = new String(homeDirPath); - if (SystemUtils.IS_OS_MAC_OSX) { + if (SystemUtils.IS_OS_LINUX) { + configDirPath += "/.config/werender"; + } else if (SystemUtils.IS_OS_MAC_OSX) { configDirPath += "/Library/Application Support/werender"; + } else if (SystemUtils.IS_OS_WINDOWS) { + configDirPath += "/werender"; } else { log.fatal(String.format("Unsupported platform")); } - /* Read werender.config file. */ + // Read werender.config file. try { String serverAddress = Config.DEFAULT_SERVER_ADDRESS; @@ -64,7 +68,7 @@ public static Config create() { e.printStackTrace(); } - /* Read werender.key file. */ + // Read werender.key file. try { String keyFilePath = configDirPath + "/werender.key"; String key = Files.readString(Paths.get(keyFilePath)); @@ -75,7 +79,7 @@ public static Config create() { e.printStackTrace(); } - /* Return instance. */ + // Return instance. return config; }