Created
December 21, 2017 15:31
-
-
Save KenMcCartney/ab73e4fd0c92c1238a360d2fe9d3008b to your computer and use it in GitHub Desktop.
Curl File Upload
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ============================================================================ | |
// | |
// Copyright (c) 2006-2015, Talend Inc. | |
// | |
// This source code has been automatically generated by_Talend Open Studio for Data Integration | |
// / Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
package shared.curl_upload_0_1; | |
import routines.Numeric; | |
import routines.DataOperation; | |
import routines.TalendDataGenerator; | |
import routines.TalendStringUtil; | |
import routines.TalendString; | |
import routines.StringHandling; | |
import routines.Relational; | |
import routines.TalendDate; | |
import routines.Mathematical; | |
import routines.system.*; | |
import routines.system.api.*; | |
import java.text.ParseException; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
import java.util.List; | |
import java.math.BigDecimal; | |
import java.io.ByteArrayOutputStream; | |
import java.io.ByteArrayInputStream; | |
import java.io.DataInputStream; | |
import java.io.DataOutputStream; | |
import java.io.ObjectOutputStream; | |
import java.io.ObjectInputStream; | |
import java.io.IOException; | |
import java.util.Comparator; | |
@SuppressWarnings("unused") | |
/** | |
* Job: curl_upload Purpose: <br> | |
* Description: <br> | |
* @author user@talend.com | |
* @version 6.4.1.20170623_1246 | |
* @status | |
*/ | |
public class curl_upload implements TalendJob { | |
public final Object obj = new Object(); | |
// for transmiting parameters purpose | |
private Object valueObject = null; | |
public Object getValueObject() { | |
return this.valueObject; | |
} | |
public void setValueObject(Object valueObject) { | |
this.valueObject = valueObject; | |
} | |
private final static String defaultCharset = java.nio.charset.Charset | |
.defaultCharset().name(); | |
private final static String utf8Charset = "UTF-8"; | |
// contains type for every context property | |
public class PropertiesWithType extends java.util.Properties { | |
private static final long serialVersionUID = 1L; | |
private java.util.Map<String, String> propertyTypes = new java.util.HashMap<>(); | |
public PropertiesWithType(java.util.Properties properties) { | |
super(properties); | |
} | |
public PropertiesWithType() { | |
super(); | |
} | |
public void setContextType(String key, String type) { | |
propertyTypes.put(key, type); | |
} | |
public String getContextType(String key) { | |
return propertyTypes.get(key); | |
} | |
} | |
// create and load default properties | |
private java.util.Properties defaultProps = new java.util.Properties(); | |
// create application properties with default | |
public class ContextProperties extends PropertiesWithType { | |
private static final long serialVersionUID = 1L; | |
public ContextProperties(java.util.Properties properties) { | |
super(properties); | |
} | |
public ContextProperties() { | |
super(); | |
} | |
public void synchronizeContext() { | |
} | |
} | |
private ContextProperties context = new ContextProperties(); | |
public ContextProperties getContext() { | |
return this.context; | |
} | |
private final String jobVersion = "0.1"; | |
private final String jobName = "curl_upload"; | |
private final String projectName = "SHARED"; | |
public Integer errorCode = null; | |
private String currentComponent = ""; | |
private final java.util.Map<String, Object> globalMap = new java.util.HashMap<String, Object>(); | |
private final static java.util.Map<String, Object> junitGlobalMap = new java.util.HashMap<String, Object>(); | |
private final java.util.Map<String, Long> start_Hash = new java.util.HashMap<String, Long>(); | |
private final java.util.Map<String, Long> end_Hash = new java.util.HashMap<String, Long>(); | |
private final java.util.Map<String, Boolean> ok_Hash = new java.util.HashMap<String, Boolean>(); | |
public final java.util.List<String[]> globalBuffer = new java.util.ArrayList<String[]>(); | |
// OSGi DataSource | |
private final static String KEY_DB_DATASOURCES = "KEY_DB_DATASOURCES"; | |
public void setDataSources( | |
java.util.Map<String, javax.sql.DataSource> dataSources) { | |
java.util.Map<String, routines.system.TalendDataSource> talendDataSources = new java.util.HashMap<String, routines.system.TalendDataSource>(); | |
for (java.util.Map.Entry<String, javax.sql.DataSource> dataSourceEntry : dataSources | |
.entrySet()) { | |
talendDataSources.put( | |
dataSourceEntry.getKey(), | |
new routines.system.TalendDataSource(dataSourceEntry | |
.getValue())); | |
} | |
globalMap.put(KEY_DB_DATASOURCES, talendDataSources); | |
} | |
private final java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); | |
private final java.io.PrintStream errorMessagePS = new java.io.PrintStream( | |
new java.io.BufferedOutputStream(baos)); | |
public String getExceptionStackTrace() { | |
if ("failure".equals(this.getStatus())) { | |
errorMessagePS.flush(); | |
return baos.toString(); | |
} | |
return null; | |
} | |
private Exception exception; | |
public Exception getException() { | |
if ("failure".equals(this.getStatus())) { | |
return this.exception; | |
} | |
return null; | |
} | |
private class TalendException extends Exception { | |
private static final long serialVersionUID = 1L; | |
private java.util.Map<String, Object> globalMap = null; | |
private Exception e = null; | |
private String currentComponent = null; | |
private String virtualComponentName = null; | |
public void setVirtualComponentName(String virtualComponentName) { | |
this.virtualComponentName = virtualComponentName; | |
} | |
private TalendException(Exception e, String errorComponent, | |
final java.util.Map<String, Object> globalMap) { | |
this.currentComponent = errorComponent; | |
this.globalMap = globalMap; | |
this.e = e; | |
} | |
public Exception getException() { | |
return this.e; | |
} | |
public String getCurrentComponent() { | |
return this.currentComponent; | |
} | |
public String getExceptionCauseMessage(Exception e) { | |
Throwable cause = e; | |
String message = null; | |
int i = 10; | |
while (null != cause && 0 < i--) { | |
message = cause.getMessage(); | |
if (null == message) { | |
cause = cause.getCause(); | |
} else { | |
break; | |
} | |
} | |
if (null == message) { | |
message = e.getClass().getName(); | |
} | |
return message; | |
} | |
@Override | |
public void printStackTrace() { | |
if (!(e instanceof TalendException || e instanceof TDieException)) { | |
if (virtualComponentName != null | |
&& currentComponent.indexOf(virtualComponentName + "_") == 0) { | |
globalMap.put(virtualComponentName + "_ERROR_MESSAGE", | |
getExceptionCauseMessage(e)); | |
} | |
globalMap.put(currentComponent + "_ERROR_MESSAGE", | |
getExceptionCauseMessage(e)); | |
System.err.println("Exception in component " + currentComponent | |
+ " (" + jobName + ")"); | |
} | |
if (!(e instanceof TDieException)) { | |
if (e instanceof TalendException) { | |
e.printStackTrace(); | |
} else { | |
e.printStackTrace(); | |
e.printStackTrace(errorMessagePS); | |
curl_upload.this.exception = e; | |
} | |
} | |
if (!(e instanceof TalendException)) { | |
try { | |
for (java.lang.reflect.Method m : this.getClass() | |
.getEnclosingClass().getMethods()) { | |
if (m.getName().compareTo(currentComponent + "_error") == 0) { | |
m.invoke(curl_upload.this, new Object[] { e, | |
currentComponent, globalMap }); | |
break; | |
} | |
} | |
if (!(e instanceof TDieException)) { | |
} | |
} catch (Exception e) { | |
this.e.printStackTrace(); | |
} | |
} | |
} | |
} | |
public void tSystem_1_error(Exception exception, String errorComponent, | |
final java.util.Map<String, Object> globalMap) | |
throws TalendException { | |
end_Hash.put(errorComponent, System.currentTimeMillis()); | |
status = "failure"; | |
tSystem_1_onSubJobError(exception, errorComponent, globalMap); | |
} | |
public void tSystem_1_onSubJobError(Exception exception, | |
String errorComponent, final java.util.Map<String, Object> globalMap) | |
throws TalendException { | |
resumeUtil.addLog("SYSTEM_LOG", "NODE:" + errorComponent, "", Thread | |
.currentThread().getId() + "", "FATAL", "", | |
exception.getMessage(), | |
ResumeUtil.getExceptionStackTrace(exception), ""); | |
} | |
public void tSystem_1Process(final java.util.Map<String, Object> globalMap) | |
throws TalendException { | |
globalMap.put("tSystem_1_SUBPROCESS_STATE", 0); | |
final boolean execStat = this.execStat; | |
String iterateId = ""; | |
String currentComponent = ""; | |
java.util.Map<String, Object> resourceMap = new java.util.HashMap<String, Object>(); | |
try { | |
String currentMethodName = new java.lang.Exception() | |
.getStackTrace()[0].getMethodName(); | |
boolean resumeIt = currentMethodName.equals(resumeEntryMethodName); | |
if (resumeEntryMethodName == null || resumeIt || globalResumeTicket) {// start | |
// the | |
// resume | |
globalResumeTicket = true; | |
/** | |
* [tSystem_1 begin ] start | |
*/ | |
ok_Hash.put("tSystem_1", false); | |
start_Hash.put("tSystem_1", System.currentTimeMillis()); | |
currentComponent = "tSystem_1"; | |
int tos_count_tSystem_1 = 0; | |
class BytesLimit65535_tSystem_1 { | |
public void limitLog4jByte() throws Exception { | |
} | |
} | |
new BytesLimit65535_tSystem_1().limitLog4jByte(); | |
Runtime runtime_tSystem_1 = Runtime.getRuntime(); | |
String[] env_tSystem_1 = null; | |
java.util.Map<String, String> envMap_tSystem_1 = System | |
.getenv(); | |
java.util.Map<String, String> envMapClone_tSystem_1 = new java.util.HashMap(); | |
envMapClone_tSystem_1.putAll(envMap_tSystem_1); | |
final Process ps_tSystem_1 = runtime_tSystem_1 | |
.exec("<curl location> -s -X POST -H Oauth-Token:<sugartoken> -H Cache-Control:no-cache -F \"filename=@<filepath>\" https://crm.test.com/rest/v10/Notes/<noteid>/file/filename", | |
env_tSystem_1); | |
globalMap.remove("tSystem_1_OUTPUT"); | |
globalMap.remove("tSystem_1_ERROROUTPUT"); | |
Thread normal_tSystem_1 = new Thread() { | |
public void run() { | |
try { | |
java.io.BufferedReader reader = new java.io.BufferedReader( | |
new java.io.InputStreamReader( | |
ps_tSystem_1.getInputStream())); | |
String line = ""; | |
try { | |
while ((line = reader.readLine()) != null) { | |
System.out.println(line); | |
} | |
} finally { | |
reader.close(); | |
} | |
} catch (java.io.IOException ioe) { | |
ioe.printStackTrace(); | |
} | |
} | |
}; | |
normal_tSystem_1.start(); | |
Thread error_tSystem_1 = new Thread() { | |
public void run() { | |
try { | |
java.io.BufferedReader reader = new java.io.BufferedReader( | |
new java.io.InputStreamReader( | |
ps_tSystem_1.getErrorStream())); | |
String line = ""; | |
try { | |
while ((line = reader.readLine()) != null) { | |
System.err.println(line); | |
} | |
} finally { | |
reader.close(); | |
} | |
} catch (java.io.IOException ioe) { | |
ioe.printStackTrace(); | |
} | |
} | |
}; | |
error_tSystem_1.start(); | |
if (ps_tSystem_1.getOutputStream() != null) { | |
ps_tSystem_1.getOutputStream().close(); | |
} | |
ps_tSystem_1.waitFor(); | |
normal_tSystem_1.join(10000); | |
error_tSystem_1.join(10000); | |
/** | |
* [tSystem_1 begin ] stop | |
*/ | |
/** | |
* [tSystem_1 main ] start | |
*/ | |
currentComponent = "tSystem_1"; | |
tos_count_tSystem_1++; | |
/** | |
* [tSystem_1 main ] stop | |
*/ | |
/** | |
* [tSystem_1 end ] start | |
*/ | |
currentComponent = "tSystem_1"; | |
globalMap.put("tSystem_1_EXIT_VALUE", ps_tSystem_1.exitValue()); | |
ok_Hash.put("tSystem_1", true); | |
end_Hash.put("tSystem_1", System.currentTimeMillis()); | |
/** | |
* [tSystem_1 end ] stop | |
*/ | |
}// end the resume | |
} catch (java.lang.Exception e) { | |
TalendException te = new TalendException(e, currentComponent, | |
globalMap); | |
throw te; | |
} catch (java.lang.Error error) { | |
throw error; | |
} finally { | |
try { | |
/** | |
* [tSystem_1 finally ] start | |
*/ | |
currentComponent = "tSystem_1"; | |
/** | |
* [tSystem_1 finally ] stop | |
*/ | |
} catch (java.lang.Exception e) { | |
// ignore | |
} catch (java.lang.Error error) { | |
// ignore | |
} | |
resourceMap = null; | |
} | |
globalMap.put("tSystem_1_SUBPROCESS_STATE", 1); | |
} | |
public String resuming_logs_dir_path = null; | |
public String resuming_checkpoint_path = null; | |
public String parent_part_launcher = null; | |
private String resumeEntryMethodName = null; | |
private boolean globalResumeTicket = false; | |
public boolean watch = false; | |
// portStats is null, it means don't execute the statistics | |
public Integer portStats = null; | |
public int portTraces = 4334; | |
public String clientHost; | |
public String defaultClientHost = "localhost"; | |
public String contextStr = "Default"; | |
public boolean isDefaultContext = true; | |
public String pid = "0"; | |
public String rootPid = null; | |
public String fatherPid = null; | |
public String fatherNode = null; | |
public long startTime = 0; | |
public boolean isChildJob = false; | |
public String log4jLevel = ""; | |
private boolean execStat = true; | |
private ThreadLocal<java.util.Map<String, String>> threadLocal = new ThreadLocal<java.util.Map<String, String>>() { | |
protected java.util.Map<String, String> initialValue() { | |
java.util.Map<String, String> threadRunResultMap = new java.util.HashMap<String, String>(); | |
threadRunResultMap.put("errorCode", null); | |
threadRunResultMap.put("status", ""); | |
return threadRunResultMap; | |
}; | |
}; | |
private PropertiesWithType context_param = new PropertiesWithType(); | |
public java.util.Map<String, Object> parentContextMap = new java.util.HashMap<String, Object>(); | |
public String status = ""; | |
public static void main(String[] args) { | |
final curl_upload curl_uploadClass = new curl_upload(); | |
int exitCode = curl_uploadClass.runJobInTOS(args); | |
System.exit(exitCode); | |
} | |
public String[][] runJob(String[] args) { | |
int exitCode = runJobInTOS(args); | |
String[][] bufferValue = new String[][] { { Integer.toString(exitCode) } }; | |
return bufferValue; | |
} | |
public boolean hastBufferOutputComponent() { | |
boolean hastBufferOutput = false; | |
return hastBufferOutput; | |
} | |
public int runJobInTOS(String[] args) { | |
// reset status | |
status = ""; | |
String lastStr = ""; | |
for (String arg : args) { | |
if (arg.equalsIgnoreCase("--context_param")) { | |
lastStr = arg; | |
} else if (lastStr.equals("")) { | |
evalParam(arg); | |
} else { | |
evalParam(lastStr + " " + arg); | |
lastStr = ""; | |
} | |
} | |
if (clientHost == null) { | |
clientHost = defaultClientHost; | |
} | |
if (pid == null || "0".equals(pid)) { | |
pid = TalendString.getAsciiRandomString(6); | |
} | |
if (rootPid == null) { | |
rootPid = pid; | |
} | |
if (fatherPid == null) { | |
fatherPid = pid; | |
} else { | |
isChildJob = true; | |
} | |
try { | |
// call job/subjob with an existing context, like: | |
// --context=production. if without this parameter, there will use | |
// the default context instead. | |
java.io.InputStream inContext = curl_upload.class.getClassLoader() | |
.getResourceAsStream( | |
"shared/curl_upload_0_1/contexts/" + contextStr | |
+ ".properties"); | |
if (isDefaultContext && inContext == null) { | |
} else { | |
if (inContext != null) { | |
// defaultProps is in order to keep the original context | |
// value | |
defaultProps.load(inContext); | |
inContext.close(); | |
context = new ContextProperties(defaultProps); | |
} else { | |
// print info and job continue to run, for case: | |
// context_param is not empty. | |
System.err.println("Could not find the context " | |
+ contextStr); | |
} | |
} | |
if (!context_param.isEmpty()) { | |
context.putAll(context_param); | |
// set types for params from parentJobs | |
for (Object key : context_param.keySet()) { | |
String context_key = key.toString(); | |
String context_type = context_param | |
.getContextType(context_key); | |
context.setContextType(context_key, context_type); | |
} | |
} | |
} catch (java.io.IOException ie) { | |
System.err.println("Could not load context " + contextStr); | |
ie.printStackTrace(); | |
} | |
// get context value from parent directly | |
if (parentContextMap != null && !parentContextMap.isEmpty()) { | |
} | |
// Resume: init the resumeUtil | |
resumeEntryMethodName = ResumeUtil | |
.getResumeEntryMethodName(resuming_checkpoint_path); | |
resumeUtil = new ResumeUtil(resuming_logs_dir_path, isChildJob, rootPid); | |
resumeUtil.initCommonInfo(pid, rootPid, fatherPid, projectName, | |
jobName, contextStr, jobVersion); | |
List<String> parametersToEncrypt = new java.util.ArrayList<String>(); | |
// Resume: jobStart | |
resumeUtil.addLog("JOB_STARTED", "JOB:" + jobName, | |
parent_part_launcher, Thread.currentThread().getId() + "", "", | |
"", "", "", | |
resumeUtil.convertToJsonText(context, parametersToEncrypt)); | |
java.util.concurrent.ConcurrentHashMap<Object, Object> concurrentHashMap = new java.util.concurrent.ConcurrentHashMap<Object, Object>(); | |
globalMap.put("concurrentHashMap", concurrentHashMap); | |
long startUsedMemory = Runtime.getRuntime().totalMemory() | |
- Runtime.getRuntime().freeMemory(); | |
long endUsedMemory = 0; | |
long end = 0; | |
startTime = System.currentTimeMillis(); | |
this.globalResumeTicket = true;// to run tPreJob | |
this.globalResumeTicket = false;// to run others jobs | |
try { | |
errorCode = null; | |
tSystem_1Process(globalMap); | |
if (!"failure".equals(status)) { | |
status = "end"; | |
} | |
} catch (TalendException e_tSystem_1) { | |
globalMap.put("tSystem_1_SUBPROCESS_STATE", -1); | |
e_tSystem_1.printStackTrace(); | |
} | |
this.globalResumeTicket = true;// to run tPostJob | |
end = System.currentTimeMillis(); | |
if (watch) { | |
System.out.println((end - startTime) + " milliseconds"); | |
} | |
endUsedMemory = Runtime.getRuntime().totalMemory() | |
- Runtime.getRuntime().freeMemory(); | |
if (false) { | |
System.out.println((endUsedMemory - startUsedMemory) | |
+ " bytes memory increase when running : curl_upload"); | |
} | |
int returnCode = 0; | |
if (errorCode == null) { | |
returnCode = status != null && status.equals("failure") ? 1 : 0; | |
} else { | |
returnCode = errorCode.intValue(); | |
} | |
resumeUtil.addLog("JOB_ENDED", "JOB:" + jobName, parent_part_launcher, | |
Thread.currentThread().getId() + "", "", "" + returnCode, "", | |
"", ""); | |
return returnCode; | |
} | |
// only for OSGi env | |
public void destroy() { | |
} | |
private java.util.Map<String, Object> getSharedConnections4REST() { | |
java.util.Map<String, Object> connections = new java.util.HashMap<String, Object>(); | |
return connections; | |
} | |
private void evalParam(String arg) { | |
if (arg.startsWith("--resuming_logs_dir_path")) { | |
resuming_logs_dir_path = arg.substring(25); | |
} else if (arg.startsWith("--resuming_checkpoint_path")) { | |
resuming_checkpoint_path = arg.substring(27); | |
} else if (arg.startsWith("--parent_part_launcher")) { | |
parent_part_launcher = arg.substring(23); | |
} else if (arg.startsWith("--watch")) { | |
watch = true; | |
} else if (arg.startsWith("--stat_port=")) { | |
String portStatsStr = arg.substring(12); | |
if (portStatsStr != null && !portStatsStr.equals("null")) { | |
portStats = Integer.parseInt(portStatsStr); | |
} | |
} else if (arg.startsWith("--trace_port=")) { | |
portTraces = Integer.parseInt(arg.substring(13)); | |
} else if (arg.startsWith("--client_host=")) { | |
clientHost = arg.substring(14); | |
} else if (arg.startsWith("--context=")) { | |
contextStr = arg.substring(10); | |
isDefaultContext = false; | |
} else if (arg.startsWith("--father_pid=")) { | |
fatherPid = arg.substring(13); | |
} else if (arg.startsWith("--root_pid=")) { | |
rootPid = arg.substring(11); | |
} else if (arg.startsWith("--father_node=")) { | |
fatherNode = arg.substring(14); | |
} else if (arg.startsWith("--pid=")) { | |
pid = arg.substring(6); | |
} else if (arg.startsWith("--context_type")) { | |
String keyValue = arg.substring(15); | |
int index = -1; | |
if (keyValue != null && (index = keyValue.indexOf('=')) > -1) { | |
if (fatherPid == null) { | |
context_param.setContextType(keyValue.substring(0, index), | |
replaceEscapeChars(keyValue.substring(index + 1))); | |
} else { // the subjob won't escape the especial chars | |
context_param.setContextType(keyValue.substring(0, index), | |
keyValue.substring(index + 1)); | |
} | |
} | |
} else if (arg.startsWith("--context_param")) { | |
String keyValue = arg.substring(16); | |
int index = -1; | |
if (keyValue != null && (index = keyValue.indexOf('=')) > -1) { | |
if (fatherPid == null) { | |
context_param.put(keyValue.substring(0, index), | |
replaceEscapeChars(keyValue.substring(index + 1))); | |
} else { // the subjob won't escape the especial chars | |
context_param.put(keyValue.substring(0, index), | |
keyValue.substring(index + 1)); | |
} | |
} | |
} else if (arg.startsWith("--log4jLevel=")) { | |
log4jLevel = arg.substring(13); | |
} | |
} | |
private static final String NULL_VALUE_EXPRESSION_IN_COMMAND_STRING_FOR_CHILD_JOB_ONLY = "<TALEND_NULL>"; | |
private final String[][] escapeChars = { { "\\\\", "\\" }, { "\\n", "\n" }, | |
{ "\\'", "\'" }, { "\\r", "\r" }, { "\\f", "\f" }, { "\\b", "\b" }, | |
{ "\\t", "\t" } }; | |
private String replaceEscapeChars(String keyValue) { | |
if (keyValue == null || ("").equals(keyValue.trim())) { | |
return keyValue; | |
} | |
StringBuilder result = new StringBuilder(); | |
int currIndex = 0; | |
while (currIndex < keyValue.length()) { | |
int index = -1; | |
// judege if the left string includes escape chars | |
for (String[] strArray : escapeChars) { | |
index = keyValue.indexOf(strArray[0], currIndex); | |
if (index >= 0) { | |
result.append(keyValue.substring(currIndex, | |
index + strArray[0].length()).replace(strArray[0], | |
strArray[1])); | |
currIndex = index + strArray[0].length(); | |
break; | |
} | |
} | |
// if the left string doesn't include escape chars, append the left | |
// into the result | |
if (index < 0) { | |
result.append(keyValue.substring(currIndex)); | |
currIndex = currIndex + keyValue.length(); | |
} | |
} | |
return result.toString(); | |
} | |
public Integer getErrorCode() { | |
return errorCode; | |
} | |
public String getStatus() { | |
return status; | |
} | |
ResumeUtil resumeUtil = null; | |
} | |
/************************************************************************************************ | |
* 23811 characters generated by Talend Open Studio for Data Integration on the | |
* December 21, 2017 10:29:24 AM EST | |
************************************************************************************************/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment