Skip to content

Commit

Permalink
Add driver for shell command #32
Browse files Browse the repository at this point in the history
Initial version of the driver
  • Loading branch information
ejboy committed Jan 31, 2020
1 parent 326df70 commit 1595dc0
Show file tree
Hide file tree
Showing 14 changed files with 987 additions and 0 deletions.
34 changes: 34 additions & 0 deletions drivers/src/java/scriptella/driver/shell/Driver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2006-2020 The Scriptella Project Team.
*
* 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 scriptella.driver.shell;

import scriptella.spi.AbstractScriptellaDriver;
import scriptella.spi.Connection;
import scriptella.spi.ConnectionParameters;

/**
* Shell scripts driver.
* <p>For configuration details and examples see <a href="package-summary.html">overview page</a>.
*
* @author Fyodor Kupolov
* @version 1.0
*/
public class Driver extends AbstractScriptellaDriver {

public Connection connect(ConnectionParameters connectionParameters) {
return new ShellConnection(new ShellConnectionParameters(connectionParameters));
}
}
137 changes: 137 additions & 0 deletions drivers/src/java/scriptella/driver/shell/ShellCommandRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright 2006-2020 The Scriptella Project Team.
*
* 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 scriptella.driver.shell;

import scriptella.util.IOUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

/**
* Runner for shell commands, abstracting threading and process details.
*/
public class ShellCommandRunner {
private final ExecutorService execService;
private String[] fullArgs;
private final BufferedWriter out;
private Process proc;
protected BufferedReader procOutputReader;
protected BufferedReader procErrReader;
private AtomicReference<Throwable> readInputError;
private AtomicReference<Throwable> readErrError;
private CountDownLatch finishedProcessingStreamsSignal;

public ShellCommandRunner(final String[] shellCmdArgs, final BufferedWriter out) {
fullArgs = Arrays.copyOf(shellCmdArgs, shellCmdArgs.length + 1);
this.out = out;
execService = Executors.newFixedThreadPool(2);
}

public void exec(String cmdText) throws IOException {
// Change the actual command as a last arg
fullArgs[fullArgs.length - 1] = cmdText;
execAndInitReaders(fullArgs);
readInputError = new AtomicReference<>();
readErrError = new AtomicReference<>();
finishedProcessingStreamsSignal = new CountDownLatch(2);
execService.submit(() -> {
String s;
try {
while ((s = procOutputReader.readLine()) != null) {
out.write(s);
out.newLine();
}
out.flush();
procOutputReader.close();
} catch (Throwable throwable) {
readInputError.set(throwable);
}
finishedProcessingStreamsSignal.countDown();
});
execService.submit(() -> {
String s;
try {
while ((s = procErrReader.readLine()) != null) {
System.err.println(s);
}
procErrReader.close();
} catch (Throwable throwable) {
readErrError.set(throwable);
}
finishedProcessingStreamsSignal.countDown();
});
}

/**
* Wait for the process to finish (including stdout/stderr finished forwarding) and check for errors during processing its output
* @throws InterruptedException if the current thread is interrupted while waiting
* @throws ExecutionException if an error occurred during processing output of the process (srdin/stderr)
*/
public void waitForAndCheckExceptions() throws InterruptedException, ExecutionException {
waitForProc();
// Even though the process is done, we may still be processing buffered output from it
finishedProcessingStreamsSignal.await();
if (readInputError.get() != null) {
throw new ExecutionException("An error occurred while processing stdout of the process", readInputError.get());
}
if (readErrError.get() != null) {
throw new ExecutionException("An error occurred while processing stderr of the process", readErrError.get());
}
}

public void executeAfterStdoutStderrConsumed(Runnable runnable) {
execService.submit(() -> {
try {
finishedProcessingStreamsSignal.await();
runnable.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}

public void close() {
execService.shutdownNow();
IOUtils.closeSilently(procErrReader);
IOUtils.closeSilently(procOutputReader);
if (proc != null) {
proc.destroy();
proc = null;
}
}

/**
* Can be subclassed for testing and no use Process at all.
*/
protected void execAndInitReaders(String[] args) throws IOException {
proc = Runtime.getRuntime().exec(args);
procOutputReader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
procErrReader = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
}

protected void waitForProc() throws InterruptedException {
proc.waitFor();
}

}
97 changes: 97 additions & 0 deletions drivers/src/java/scriptella/driver/shell/ShellConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2006-2020 The Scriptella Project Team.
*
* 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 scriptella.driver.shell;

import scriptella.driver.text.AbstractTextConnection;
import scriptella.expression.PropertiesSubstitutor;
import scriptella.spi.ParametersCallback;
import scriptella.spi.ProviderException;
import scriptella.spi.QueryCallback;
import scriptella.spi.Resource;
import scriptella.util.IOUtils;

import java.io.IOException;
import java.io.Reader;

/**
* Represents a shell script connection.
* <p>For configuration details and examples see <a href="package-summary.html">overview page</a>.
*
* @author Fyodor Kupolov
* @version 1.0
*/
public class ShellConnection extends AbstractTextConnection {
private ShellScriptExecutor out;

public ShellConnection(ShellConnectionParameters parameters) {
super(parameters.getDialectIdentifier(), parameters);
}

public void executeScript(final Resource scriptContent, final ParametersCallback parametersCallback) throws ProviderException {
initScriptExecutor();
Reader reader = null;
try {
reader = scriptContent.open();
out.execute(reader, parametersCallback, counter);
if (getConnectionParameters().isFlush()) {
out.flush();
}
} catch (IOException e) {
throw new ShellProviderException("Failed to produce output", e);
} finally {
IOUtils.closeSilently(reader);
}
}

/**
* Lazily initializes script writer.
*/
protected void initScriptExecutor() {
if (out == null) {
try {
this.out = new ShellScriptExecutor(newOutputWriter(), getConnectionParameters());
} catch (IOException e) {
throw new ShellProviderException("Unable to open file " + getConnectionParameters().getUrl() + " for writing", e);
}
}
}

public void executeQuery(Resource queryContent, ParametersCallback parametersCallback, QueryCallback queryCallback) throws ProviderException {
Reader q;
try {
q = queryContent.open();
} catch (IOException e) {
throw new ShellProviderException("Cannot read a shell query", e);
}

try {
new ShellQueryExecutor(q, new PropertiesSubstitutor(parametersCallback), getConnectionParameters()).
execute(queryCallback, counter);
} finally {
IOUtils.closeSilently(q);
}
}

public void close() throws ProviderException {
IOUtils.closeSilently(out);
out = null;
}

@Override
protected ShellConnectionParameters getConnectionParameters() {
return (ShellConnectionParameters) super.getConnectionParameters();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2006-2020 The Scriptella Project Team.
*
* 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 scriptella.driver.shell;

import scriptella.driver.text.TextConnectionParameters;
import scriptella.spi.ConnectionParameters;
import scriptella.spi.DialectIdentifier;

/**
* Connection parameters for shell driver.
*
* @author Fyodor Kupolov
* @version 1.1
*/
public class ShellConnectionParameters extends TextConnectionParameters {
private ShellOs osBehavior;
private DialectIdentifier dialectIdentifier;
private String[] shellCommandArgs;

ShellConnectionParameters(ConnectionParameters parameters) {
super(parameters);
// Use os behavior param or guess from the running env
String shellOs = parameters.getStringProperty("os_behavior");
if (shellOs != null) {
osBehavior = ShellOs.fromOsNameVersion(shellOs, null);
dialectIdentifier = new DialectIdentifier(shellOs, null);
} else {
String osName = System.getProperty("os.name");
String osVersion = System.getProperty("os.version");
osBehavior = ShellOs.fromOsNameVersion(osName, osVersion);
dialectIdentifier = new DialectIdentifier(osName, osVersion);
}
String shellCmds = parameters.getStringProperty("shell_cmd");
if (shellCmds != null) {
shellCommandArgs = shellCmds.split("\\s*,\\s*");
} else if (osBehavior == ShellOs.LINUX || osBehavior == ShellOs.MAC) {
shellCommandArgs = new String[] {"/bin/sh", "-c"};
} else if (osBehavior == ShellOs.WINDOWS) {
shellCommandArgs = new String[] {"cmd.exe", "/c"};
}
}

ShellOs getOsBehavior() {
return osBehavior;
}

DialectIdentifier getDialectIdentifier() {
return dialectIdentifier;
}

String[] getShellCommandArgs() {
return shellCommandArgs;
}
}
32 changes: 32 additions & 0 deletions drivers/src/java/scriptella/driver/shell/ShellOs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2006-2020 The Scriptella Project Team.
*
* 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 scriptella.driver.shell;

/**
* Represents target shell OS
*/
public enum ShellOs {
LINUX, MAC, WINDOWS;

public static ShellOs fromOsNameVersion(String osName, String osVersion) {
if (osName.toLowerCase().startsWith("mac")) {
return MAC;
} else if (osName.toLowerCase().startsWith("windows")) {
return WINDOWS;
}
return LINUX;
}
}
Loading

0 comments on commit 1595dc0

Please sign in to comment.