package com.github.shell88.bddvideoannotator.javaadapters; import com.github.shell88.bddvideoannotator.annotationfile.converter.HtmlConverter; import com.github.shell88.bddvideoannotator.stubjava.AnnotationService; import com.github.shell88.bddvideoannotator.stubjava.AnnotationServiceService; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Paths; import java.util.Properties; import javax.xml.ws.BindingProvider; /** * Reads the Properties-File, starts the java-based server Process and * establishes a SOAP-connection to it. * * @author Hell * */ public class ServerConnector { private AnnotationService serverClient = null; private Process serverProcess = null; private String publishAddress; private String videoWidth; private String videoHeight; private String outputDirectory; private Boolean convert2html; /** * Initializes ServerConnector with Properties from properties_file. */ public ServerConnector() { Properties properties = loadPropertiesFromConfigFile(); Integer port = Integer.parseInt(properties.getProperty("publish_port")); this.publishAddress = "http://localhost:" + port + "/bdd_videoannotator"; videoWidth = properties.getProperty("video_width"); videoHeight = properties.getProperty("video_height"); outputDirectory = properties.getProperty("output_directory"); convert2html = Boolean.valueOf(properties.getProperty("convert2html")); } /** * Loads properties from either adapter_config.propertes (custom_setting, * preferred file when found on the classpath) or the * default_config.properties (always included in the server JAR-Package). * * @return loaded Properties */ public Properties loadPropertiesFromConfigFile() { InputStream instream = ClassLoader .getSystemResourceAsStream("adapter_config.properties"); if (instream == null) { instream = ClassLoader .getSystemResourceAsStream("default_config.properties"); } Properties properties = new Properties(); try { properties.load(instream); instream.close(); } catch (IOException e) { throw new ServerConnectorException("Could not read Properties-File", e); } return properties; } /** * Starts the server in a separate JVM-Process. A shutdown hook will be * registered so that the JVM Process will be securely terminated when the * adapter ends. * * @return A java SOAP client to the server */ public synchronized AnnotationService startServerProcess() { if (serverProcess == null) { ProcessBuilder serverProcessBuilder = new ProcessBuilder( getCommandForServerStart()); try { serverProcess = serverProcessBuilder.start(); } catch (IOException e1) { throw new ServerConnectorException("Could not start ServerProcess", e1); } Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { stopServerProcess(); convert2HtmlIfSet(); } catch (Exception e) { e.printStackTrace(); } } }); } return getServerClient(); } /** * @return - Singleton SOAP-Client to the annotation server. */ public synchronized AnnotationService getServerClient() { if (serverClient != null) { return serverClient; } URL connectionUrl; try { connectionUrl = new URL(getWsdlLocation()); } catch (MalformedURLException e) { throw new ServerConnectorException("Problem with WSDL-Location " + getWsdlLocation(), e); } int sleepMilliseconds = 100; for (int retries = 0; retries < 30; retries++) { try { if (serverProcess.getErrorStream().available() > 0) { break; } } catch (IOException e2) { //nothing to do here } try { serverClient = new AnnotationServiceService(connectionUrl) .getAnnotationServicePort(); BindingProvider bindingProvider = (BindingProvider) serverClient; bindingProvider.getRequestContext().put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, getPublishingAddress()); return serverClient; } catch (javax.xml.ws.WebServiceException e) { try { Thread.sleep(sleepMilliseconds); } catch (InterruptedException e1) { e1.printStackTrace(); } } } throw new ServerConnectorException( "Could not connect to server. Error Stream of Server: " + convertStreamtoString(serverProcess.getErrorStream())); } /** * Used to stop the server Process within a shutdown-hook. * * @return true if serverProcess was terminated successfully */ public synchronized boolean stopServerProcess() { if (serverClient != null) { try { serverClient.stopScenario(); } catch (javax.xml.ws.WebServiceException e) { System.err.println("Could not stopScenario: " + e.getMessage()); } } serverClient = null; if (serverProcess == null) { return true; } serverProcess.destroy(); return isProcessTerminated(2); } /** * Converts all file to an html-report from {@link #outputDirectory} into a subfolder. * Requires ffmpeg to be installed and available on the system path. */ public synchronized void convert2HtmlIfSet() { if (!convert2html) { return; } try { new HtmlConverter(this.outputDirectory, this.outputDirectory + File.separator + "html") .convert(); } catch (Throwable e) { throw new ServerConnectorException("Could not convert to html " + e.getMessage()); } } /** * Compatibility to JRE Version 1.7 Can be replaced with process.isAlive() in * JDK/JRE Version 1.8 * * @param waitSeconds * timeout * @return true when the process has exited successfully */ private boolean isProcessTerminated(int waitSeconds) { int sleepMilliseconds = 10; int repetitions = waitSeconds * 1000 / sleepMilliseconds; do { try { serverProcess.exitValue(); return true; } catch (IllegalThreadStateException ex) { try { Thread.sleep(sleepMilliseconds); } catch (InterruptedException e) { return false; } finally { --repetitions; } } } while (repetitions > 0); return false; } /** * @return The address where the server will be published. */ public String getPublishingAddress() { return publishAddress; } /** * @return The adress where the wsdl-file will be published. */ public String getWsdlLocation() { return publishAddress + "?wsdl"; } /** * @return string Array containing all arguments to start the server process. */ public String[] getCommandForServerStart() { /* * Uses non shaded (not standalone) server because dependencies for * Java-based BDD-Frameworks can be resolved using maven */ return new String[] { "java", "-cp", getCurrentClasspath(), com.github.shell88.bddvideoannotator.service.AnnotationService.class .getCanonicalName(), publishAddress, outputDirectory, videoWidth, videoHeight }; } /** * @return command-Line classpath from the current SystemClassLoader. * */ private String getCurrentClasspath() { URL[] loadedUrls = ((URLClassLoader) (ClassLoader.getSystemClassLoader())) .getURLs(); StringBuffer buffer = new StringBuffer(); String pathTemp; for (URL url : loadedUrls) { try { pathTemp = Paths.get(url.toURI()).toFile().getAbsolutePath(); } catch (URISyntaxException e) { throw new ServerConnectorException( "Could not read classpath for starting serverProcess", e); } buffer.append(pathTemp); buffer.append(System.getProperty("path.separator")); } return buffer.toString(); } /** * Converts an InputStream to a single String. * * @param stream * the input stream. * @return the string containing the contents of the stream. */ public static String convertStreamtoString(InputStream stream) { StringBuilder content = new StringBuilder(); String tempRead; BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); try { while (reader.ready() && (tempRead = reader.readLine()) != null) { content.append(tempRead); } reader.close(); return content.toString(); } catch (IOException e) { return ""; } } }