Skip to content

Commit aa16217

Browse files
committed
added class and tests for shell RuntimeInterface
1 parent 18d9e5a commit aa16217

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package net.b07z.sepia.server.core.tools;
2+
3+
import java.nio.charset.Charset;
4+
import java.nio.charset.StandardCharsets;
5+
import java.util.List;
6+
import java.util.concurrent.TimeUnit;
7+
import java.util.stream.Stream;
8+
9+
/**
10+
* Class to help with runtime executions.
11+
*
12+
* @author Florian Quirin
13+
*
14+
*/
15+
public class RuntimeInterface {
16+
17+
public static final boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
18+
public static Charset windowsShellCodepage = null;
19+
20+
/**
21+
* Hold result of a runtime command.
22+
*/
23+
public static class RuntimeResult {
24+
private int statusCode;
25+
private List<String> output;
26+
private Exception exception;
27+
28+
/**
29+
* Create new result with statusCode (kind of exit code) and output string.
30+
* @param statusCode - 0: all good, 1: general error, 2: tbd, 3: timeout
31+
* @param output - list of lines returned by execution (in correct encoding hopefully) or null
32+
* @param exception - exception catched if any (or null)
33+
*/
34+
public RuntimeResult(int statusCode, List<String> output, Exception exception){
35+
this.statusCode = statusCode;
36+
this.output = output;
37+
this.exception = exception;
38+
}
39+
/**
40+
* Return list of lines.
41+
*/
42+
public List<String> getOutput(){
43+
return output;
44+
}
45+
/**
46+
* Return status code - 0: all good, 1: general error, 2: tbd, 3: timeout
47+
*/
48+
public int getStatusCode(){
49+
return statusCode;
50+
}
51+
public Exception getException(){
52+
return exception;
53+
}
54+
@Override
55+
public String toString(){
56+
return toString(System.lineSeparator());
57+
}
58+
/**
59+
* Return output as string. If possible (not null or empty) join lines separated by 'delimiter'.
60+
* @param delimiter
61+
* @return
62+
*/
63+
public String toString(CharSequence delimiter){
64+
if (output == null){
65+
return null;
66+
}else if (output.isEmpty()){
67+
return "";
68+
}else{
69+
return String.join(delimiter, output);
70+
}
71+
}
72+
}
73+
74+
/**
75+
* We need to run the shell codepage command to get proper windows encoding (is there REALLY no BETTER WAY??).
76+
* @return
77+
*/
78+
public static RuntimeResult getWindowsShellCodepage(){
79+
RuntimeResult rtr = runCommand(new String[]{"chcp"}, 3000);
80+
List<String> out = rtr.getOutput();
81+
if (rtr.getStatusCode() == 0 && Is.notNullOrEmpty(out)){
82+
Debugger.println("RuntimeInterface - Trying to get Windows shell encoding...", 3);
83+
String joinedOut = rtr.toString().replaceAll("\\n|\\r", " ");
84+
if (joinedOut.matches(".*\\d+.*")){
85+
String encoding = joinedOut.replaceAll(".*?([0-9]+).*", "$1");
86+
windowsShellCodepage = Charset.forName("cp" + encoding);
87+
Debugger.println("RuntimeInterface - Windows shell encoding: " + windowsShellCodepage.toString(), 3);
88+
}else{
89+
return new RuntimeResult(1, null, new RuntimeException("Could not get Windows shell encoding (codepage)!"));
90+
}
91+
}else{
92+
return new RuntimeResult(1, null, new RuntimeException("Could not get Windows shell encoding (codepage)!"));
93+
}
94+
return rtr;
95+
}
96+
97+
/**
98+
* Execute runtime command. Chooses between "cmd.exe" and "sh" shell by OS.
99+
* @param command - e.g.: 'new String[]{"ping", "-c", "3", "sepia-framework.github.io"}'
100+
* @return
101+
*/
102+
public static RuntimeResult runCommand(String[] command){
103+
return runCommand(command, 5000);
104+
}
105+
public static RuntimeResult runCommand(String[] command, long customTimeout){
106+
Charset encoding = StandardCharsets.UTF_8;
107+
if (isWindows && windowsShellCodepage == null && !command[0].equals("chcp")){
108+
RuntimeResult rtr = getWindowsShellCodepage();
109+
if (rtr.getStatusCode() != 0){
110+
return new RuntimeResult(1, null, rtr.getException());
111+
}else{
112+
encoding = windowsShellCodepage;
113+
}
114+
}else if (windowsShellCodepage != null){
115+
encoding = windowsShellCodepage;
116+
}
117+
if (customTimeout > 15000){
118+
customTimeout = 15000;
119+
}
120+
Process process;
121+
try{
122+
//process = Runtime.getRuntime().exec(command); //old way
123+
ProcessBuilder builder = new ProcessBuilder();
124+
String[] osPart;
125+
if (isWindows){
126+
osPart = new String[]{"cmd.exe", "/c"};
127+
}else{
128+
osPart = new String[]{"sh", "-c"};
129+
}
130+
builder.command(Stream.of(osPart, command).flatMap(Stream::of).toArray(String[]::new)); //tricky way to concatenate arrays
131+
//builder.directory(new File(System.getProperty("user.home"))); //set process directory or other options ...
132+
process = builder.start();
133+
134+
//wait for finish or timeout
135+
if(!process.waitFor(customTimeout, TimeUnit.MILLISECONDS)){
136+
//timeout - kill the process.
137+
process.destroy(); // consider using destroyForcibly instead
138+
return new RuntimeResult(3, null, new RuntimeException("Timeout!"));
139+
}
140+
}catch (Exception e){
141+
return new RuntimeResult(1, null, e);
142+
}
143+
List<String> output = null;
144+
try{
145+
output = FilesAndStreams.getLinesFromStream(process.getInputStream(), encoding);
146+
return new RuntimeResult(process.exitValue(), output, null);
147+
}catch(Exception e){
148+
return new RuntimeResult(1, output, e);
149+
}
150+
}
151+
152+
}

src/test/java/tools/ToolsPlayground.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
import java.io.StringWriter;
55
import java.util.ArrayList;
66
import java.util.List;
7-
87
import org.json.simple.JSONObject;
98

109
import com.fasterxml.jackson.core.JsonFactory;
1110
import com.fasterxml.jackson.core.JsonGenerator;
1211

1312
import net.b07z.sepia.server.core.tools.EsQueryBuilder;
1413
import net.b07z.sepia.server.core.tools.JSON;
14+
import net.b07z.sepia.server.core.tools.RuntimeInterface;
1515
import net.b07z.sepia.server.core.tools.EsQueryBuilder.QueryElement;
16+
import net.b07z.sepia.server.core.tools.RuntimeInterface.RuntimeResult;
1617
import net.b07z.sepia.server.core.tools.Security;
1718

1819
/**
@@ -24,7 +25,43 @@ public class ToolsPlayground {
2425

2526
public static void main(String[] args) {
2627

28+
/* -- Runtime commands -- */
29+
System.out.println("Calling runtime: ");
30+
RuntimeResult rtr = RuntimeInterface.runCommand(new String[]{"chcp"}, 5000);
31+
System.out.println(rtr.toString()); if (rtr.getStatusCode() != 0) System.out.println(rtr.getException());
32+
rtr = RuntimeInterface.runCommand(new String[]{"ping", "sepia-framework.github.io"}, 5000);
33+
System.out.println(rtr.toString()); if (rtr.getStatusCode() != 0) System.out.println(rtr.getException());
34+
rtr = RuntimeInterface.runCommand(new String[]{"ping", "sepia-framework.github.io"}, 500);
35+
System.out.println(rtr.toString()); if (rtr.getStatusCode() != 0) System.out.println(rtr.getException());
36+
rtr = RuntimeInterface.runCommand(new String[]{"ping", "-c", "3", "sepia-framework.github.io"}, 5000);
37+
System.out.println(rtr.toString()); if (rtr.getStatusCode() != 0) System.out.println(rtr.getException());
38+
39+
/* -- Write test properties file -- */
40+
/*
41+
System.out.println("\nTesting properties store and load: ");
42+
Properties prop = new Properties();
43+
prop.setProperty("test", out);
44+
prop.setProperty("umlaute", "äöü");
45+
String path = System.getProperty("user.home") + "\\test.properties";
46+
try{
47+
FilesAndStreams.saveSettings(path, prop);
48+
System.out.println("Stored test-file at: " + path);
49+
}catch (Exception e){
50+
System.out.println("Failed to store test-file at: " + path);
51+
e.printStackTrace();
52+
}
53+
try{
54+
Properties prop2 = FilesAndStreams.loadSettings(path);
55+
System.out.println("Special Umlaute: " + prop2.getProperty("umlaute"));
56+
System.out.println("Test: " + prop2.getProperty("test"));
57+
}catch (Exception e){
58+
System.out.println("Failed to load test-file at: " + path);
59+
e.printStackTrace();
60+
}
61+
*/
62+
2763
/* -- Password client hash -- */
64+
/*
2865
String hash = hashPassword("testpwd12345678!_");
2966
System.out.println("password client hash: " + hash);
3067
hash = hashPassword("TestPwd12345678!_");
@@ -35,6 +72,7 @@ public static void main(String[] args) {
3572
System.out.println("password client hash: " + hash);
3673
hash = hashPassword("test12345!_");
3774
System.out.println("password client hash: " + hash);
75+
*/
3876

3977
/* -- Elasticsearch queries -- */
4078
/*

0 commit comments

Comments
 (0)