package eu.aniketos.serviceruntime.remote;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.aniketos.serviceruntime.remote.tomcat.TomcatClient;
import eu.aniketos.serviceruntime.remote.tomcat.WarCreator;
/**
* Factory of composite services
* @author Kostas Giannakakis
*
*/
public class CompositeServiceFactory {
/** Logger */
final private static Logger logger = LoggerFactory.getLogger(CompositeServiceFactory.class);
/**
* Configuration parameters
* @author Kostas Giannakakis
*
*/
public static class Config {
/** Directory, where compositions will be stored */
public String compositionsDir;
/** Address of Activiti */
public String activitiAddress;
/** Username of Activiti's user */
public String activitiUsername;
/** Password of Activiti's user */
public String activitiPassword;
/** Tomcat'a address */
public String tomcatAddress;
/** Tomcat's public address */
public String tomcatPublicAddress;
/** Username of Tomcat's user */
public String tomcatUsername;
/** Password of Tomcat's user */
public String tomcatPassword;
/** Tomcat's version */
public int tomcatVersion = 6;
}
/** Directory where a composition is stored */
private String compositionsDir;
/** Address of Activiti */
private String activitiAddress;
/** Username of Activiti's user */
private String activitiUsername;
/** Password of Activiti's user */
private String activitiPassword;
/** Tomcat's client */
private TomcatClient tomcatClient;
/** Resources directory. It stores all files (jar, xml) that should be included in the WAR */
private String resoursesDir;
/** Configuration object */
private Config config;
/**
* Constructor
* @param config The configuration of the factory
*/
public CompositeServiceFactory(Config config) {
this.config = config;
compositionsDir = config.compositionsDir;
activitiAddress = config.activitiAddress;
activitiUsername = config.activitiUsername;
activitiPassword = config.activitiPassword;
tomcatClient = new TomcatClient(config.tomcatAddress,
config.tomcatUsername, config.tomcatPassword);
tomcatClient.setTomcatVersion(config.tomcatVersion);
File compositionsDirectory = new File(compositionsDir);
compositionsDirectory.mkdir();
resoursesDir = compositionsDir + "/resources";
buildResourcesDir();
}
/**
* Creates resources directory (if it doesn't exist) and populates it with
* files from the bundle
*/
private void buildResourcesDir() {
File resDir = new File(resoursesDir);
if (!resDir.exists()) {
File libDir = new File(resoursesDir + "/lib");
if (!libDir.mkdirs()) {
logger.error("Couldn't create resources lib directory {}", libDir);
return;
}
String [] resourceNames = {
"sun-jaxws.xml",
"web.xml",
"lib/commons-codec-1.6.jar",
"lib/commons-logging-1.1.1.jar",
"lib/FastInfoset.jar",
"lib/fluent-hc-4.2.jar",
"lib/gmbal-api-only.jar",
"lib/ha-api.jar",
"lib/httpclient-4.2.jar",
"lib/httpclient-cache-4.2.jar",
"lib/httpcore-4.2.jar",
"lib/httpmime-4.2.jar",
"lib/javax.annotation.jar",
"lib/javax.mail_1.4.jar",
"lib/jaxb-api.jar",
"lib/jaxb-impl.jar",
"lib/jaxb-xjc.jar",
"lib/jaxws-api.jar",
"lib/jaxws-rt.jar",
"lib/jaxws-tools.jar",
"lib/jsr181-api.jar",
"lib/management-api.jar",
"lib/mimepull.jar",
"lib/policy.jar",
"lib/saaj-api.jar",
"lib/saaj-impl.jar",
"lib/stax-ex.jar",
"lib/stax2-api.jar",
"lib/streambuffer.jar",
"lib/woodstox-core-asl.jar",
"lib/datamanager-1.0-SNAPSHOT.jar"
};
byte [] buffer = new byte[16000];
for(String resourceName: resourceNames) {
InputStream is = getClass().getResourceAsStream("/resources/" + resourceName);
OutputStream os = null;
try {
os = new FileOutputStream(resDir + "/" + resourceName);
int length = 0;
while ( (length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
logger.debug("Copied file {}", resourceName);
} catch (FileNotFoundException e) {
logger.error("Error creating file {}: {}", resourceName, e.getMessage());
} catch (IOException e) {
logger.error("Error writing to file {}: {}", resourceName, e.getMessage());
}
finally {
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
}
catch (IOException ex) {
}
}
}
}
else {
logger.debug("Resources directory exists");
}
}
/**
* Returns the composition directory
* @param compositionPlanName Name of the composition
* @return the composition directory
*/
private String getCompositionDir(String compositionPlanName) {
return compositionsDir + "/" + compositionPlanName;
}
/**
* Returns the path of the WAR file
* @param compositionPlanName Name of the composition
* @return the path of the WAR file
*/
private String getWarPath(String compositionPlanName) {
return compositionsDir + "/" + compositionPlanName + ".war";
}
/**
* Creates a service instance
* @param compositionPlanName The name of the composition
* @param processId The process id of the Activiti process
* @param methodName The name of the method that invokes the composite service
* @return true, if the method succeeds
*/
public boolean createServiceInstance(String compositionPlanName,
String processId,
String methodName){
String compositionDir = getCompositionDir(compositionPlanName);
new File(compositionDir + "/").mkdir();
new File(compositionDir + "/WEB-INF").mkdir();
new File(compositionDir + "/WEB-INF/classes").mkdir();
new File(compositionDir + "/WEB-INF/lib").mkdir();
File serviceInterface = new File(compositionDir + "/" + compositionPlanName + ".java");
File serviceImplementation = new File(compositionDir + "/" + compositionPlanName + "Impl.java");
try
{
FileWriter serviceInterfaceFW = new FileWriter(serviceInterface);
serviceInterfaceFW.write(
"package eu.aniketos.compositeService;\n" +
"import javax.jws.WebService;\n" +
"@WebService\n" +
"public interface " + compositionPlanName + "{\n" +
"String [] " + methodName + " (String [] paramNames, String [] paramValues);\n" + "}\n"
);
serviceInterfaceFW.close();
FileWriter serviceImplementationFW = new FileWriter(serviceImplementation);
serviceImplementationFW.write(
"package eu.aniketos.compositeService;\n" +
"import javax.jws.WebService;\n" +
"import java.net.URL;\n" +
"import org.apache.http.HttpHost;\n" +
"import org.apache.http.HttpEntity;\n" +
"import org.apache.http.HttpResponse;\n" +
"import org.apache.http.auth.AuthScope;\n" +
"import org.apache.http.auth.UsernamePasswordCredentials;\n" +
"import org.apache.http.client.AuthCache;\n" +
"import org.apache.http.client.ClientProtocolException;\n" +
"import org.apache.http.client.methods.HttpPost;\n" +
"import org.apache.http.client.protocol.ClientContext;\n" +
"import org.apache.http.entity.mime.MultipartEntity;\n" +
"import org.apache.http.entity.mime.content.FileBody;\n" +
"import org.apache.http.entity.mime.content.StringBody;\n" +
"import org.apache.http.impl.auth.BasicScheme;\n" +
"import org.apache.http.impl.client.BasicAuthCache;\n" +
"import org.apache.http.impl.client.DefaultHttpClient;\n" +
"import org.apache.http.util.EntityUtils;\n" +
"import org.apache.http.protocol.BasicHttpContext;\n" +
"import org.apache.http.entity.StringEntity;\n" +
"import java.io.IOException;\n" +
"import java.util.regex.Matcher;\n" +
"import java.util.regex.Pattern;\n\n" +
"import eu.aniketos.serviceruntime.datamanager.DataManager;\n\n" +
"@WebService(endpointInterface = \"eu.aniketos.compositeService." + compositionPlanName + "\")\n" +
"public class " + compositionPlanName + "Impl implements " + compositionPlanName + " {\n" +
"public String [] " + methodName + " (String [] paramNames, String [] paramValues) {\n" +
"DefaultHttpClient httpclient = new DefaultHttpClient();\n" +
"try {\n" +
"URL url = new URL(\"" + activitiAddress + "\");\n" +
"String scheme = url.getProtocol();\n" +
"String hostName = url.getHost();\n" +
"int port = url.getPort();\n" +
"HttpHost targetHost = new HttpHost(hostName, port, scheme);\n" +
"httpclient.getCredentialsProvider().setCredentials(\n" +
"new AuthScope(targetHost.getHostName(), targetHost.getPort()),new UsernamePasswordCredentials(\"" + activitiUsername + "\", \"" + activitiPassword + "\"));\n" +
"AuthCache authCache = new BasicAuthCache();\n" +
"BasicScheme basicAuth = new BasicScheme();\n" +
"authCache.put(targetHost, basicAuth);\n" +
"BasicHttpContext localcontext = new BasicHttpContext();\n" +
"localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache);\n" +
"HttpPost httpPost = new HttpPost(\"" + activitiAddress + "/activiti-rest/service/process-instance\");\n" +
"String requestBody=\"\\\"processDefinitionId\\\":\\\"\" + DataManager.getProcessId(\"" + compositionPlanName + "\") + \"\\\"\";\n" +
"if (paramNames != null && paramValues != null && paramNames.length == paramValues.length) {\n" +
"for(int i=0;i<paramNames.length;i++) {\n" +
"requestBody += \",\\\"\" + paramNames[i] + \"\\\":\\\"\" + paramValues[i] + \"\\\"\";\n" +
"}\n}\n" +
"System.out.println(\"{\" + requestBody + \"}\");" +
"StringEntity input = new StringEntity(\"{\" + requestBody + \"}\");\n" +
"input.setContentType(\"application/json\");\n" +
"httpPost.setEntity(input);\n" +
"HttpResponse httpResponse = httpclient.execute(httpPost, localcontext);\n" +
"HttpEntity responseEntity = httpResponse.getEntity();\n" +
"if(responseEntity!=null) {\n" +
" String response = EntityUtils.toString(responseEntity);\n" +
" System.out.println(\"******* \" + response);\n" +
" if (response != null) {\n" +
" String instanceId = getId(response);\n" +
" if (instanceId != null) {\n" +
" System.out.println(\"Getting results for: \" + instanceId);\n" +
" return DataManager.getResults(instanceId);\n" +
" }\n" +
" }\n"+
"}\n" +
"} catch (ClientProtocolException e) {\n" +
"e.printStackTrace();\n" +
"} catch (IOException e) {\n" +
"e.printStackTrace();\n" +
"} finally {\n" +
"try { httpclient.getConnectionManager().shutdown(); } catch (Exception ignore) {}\n" +
"}\n" +
"return null;\n" +
"}\n" +
" private Pattern idPattern = Pattern.compile(\"\\\"id\\\":\\\"(.+?)\\\"\");\n" +
" \n" +
" private String getId(String json) {\n" +
" json = json.replace('\\n', ' ');\n" +
" json = json.replace('\\r', ' ');\n" +
" \n" +
" Matcher matcher = idPattern.matcher(json);\n" +
" if (matcher.find()) {\n" +
" return matcher.group(1);\n" +
" }\n" +
" \n" +
" return null;\n" +
" }" +
"}"
);
serviceImplementationFW.close();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
logger.error("Compiler is null!");
}
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
if (fileManager == null) {
logger.error("File manager is null!");
}
fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
Arrays.asList(new File(compositionDir + "/WEB-INF/classes")));
fileManager.setLocation(StandardLocation.CLASS_PATH,
Arrays.asList(new File(resoursesDir + "/lib/httpclient-4.2.jar"),
new File(resoursesDir + "/lib/httpcore-4.2.jar"),
new File(resoursesDir + "/lib/httpmime-4.2.jar"),
new File(resoursesDir + "/lib/datamanager-1.0-SNAPSHOT.jar"),
new File(compositionDir + "/WEB-INF/classes")));
logger.debug("Starting compilation...");
// Compile the interface java file of the composition instance
String [] options = {"-source", "1.6", "-target", "1.6"};
compiler.getTask(null, fileManager, null,
Arrays.asList(options), null,
fileManager.getJavaFileObjectsFromFiles(Arrays.asList(serviceInterface))).call();
// Compile the DataManager java file of the composition instance
//compiler.getTask(null, fileManager, null, null, null,
// fileManager.getJavaFileObjectsFromFiles(Arrays.asList(dataManager))).call();
// Compile the implementation java file of the composition instance
compiler.getTask(null, fileManager, null,
Arrays.asList(options), null,
fileManager.getJavaFileObjectsFromFiles(Arrays.asList(serviceImplementation))).call();
fileManager.close();
serviceInterface.delete();
serviceImplementation.delete();
logger.debug("Service Instance created");
return true;
}
catch (IOException e)
{
logger.error("IOException: {}", e.getMessage());
return false;
}
}
/**
* Creates the WAR file of the service
* @param compositionPlanName The name of the composition
* @return true, if the WAR file is created
*/
public boolean createWar(String compositionPlanName){
try {
File webXmlResources = new File(resoursesDir, "web.xml");
File webXmlWar = new File(getCompositionDir(compositionPlanName) +
"/WEB-INF/web.xml");
File sunXmlResources = new File(resoursesDir, "sun-jaxws.xml");
File sunXmlWar = new File(getCompositionDir(compositionPlanName) +
"/WEB-INF/sun-jaxws.xml");
copyFile(webXmlResources, webXmlWar);
copyFile(sunXmlResources, sunXmlWar);
modifySunXmlFile(compositionPlanName);
//copy lib directory
File dirTarget = new File(getCompositionDir(compositionPlanName) +
"/WEB-INF/lib");
File dirSource = new File(resoursesDir + "/lib/");
copyDirectory(dirSource, dirTarget);
WarCreator warCreator = new WarCreator();
warCreator.create(getCompositionDir(compositionPlanName) + "/WEB-INF/",
getWarPath(compositionPlanName));
logger.debug("WAR file created");
return true;
} catch (Exception ex) {
logger.error("Exception", ex);
logger.error("WAR file not created");
return false;
}
}
public static void copyFile(File source, File dest) throws IOException
{
File inputFile = source;
File outputFile = dest;
InputStream finput = new BufferedInputStream(new FileInputStream(inputFile));
OutputStream foutput = new BufferedOutputStream(new FileOutputStream(outputFile));
byte[] buffer = new byte[1024 * 500];
int bytes_read = 0;
while((bytes_read = finput.read(buffer)) > 0)
foutput.write(buffer, 0, bytes_read);
finput.close();
foutput.close();
}
public static void copyDirectory(File source, File dest) throws IOException
{
File[] listFiles = source.listFiles();
for(int i = 0; i < listFiles.length; i++)
{
File targetFile = new File(dest.getAbsolutePath()+"/"+listFiles[i].getName());
copyFile(listFiles[i],targetFile);
}
}
/**
* Modifies SUN's XML file
* @param compositionPlanName The name of the composition
* @throws JDOMException
* @throws IOException
*/
private void modifySunXmlFile(String compositionPlanName) throws JDOMException, IOException{
// Istantiating a not validating XML parser
SAXBuilder builder = new SAXBuilder(false);
// creating a DOM structure into the memory starting from the XML file
Document document = builder.build(
new File(getCompositionDir(compositionPlanName) + "/WEB-INF/sun-jaxws.xml"));
Element root = document.getRootElement();
Namespace ns = root.getNamespace("http://java.sun.com/xml/ns/jax-ws/ri/runtime");
Element endpoint = root.getChild("endpoint",ns);
endpoint.setAttribute("implementation", "eu.aniketos.compositeService." +
compositionPlanName + "Impl");
endpoint.setAttribute("name", compositionPlanName);
XMLOutputter outputter = new XMLOutputter();
outputter.setFormat(Format.getPrettyFormat());
FileOutputStream fos = new FileOutputStream(
getCompositionDir(compositionPlanName) + "/WEB-INF/sun-jaxws.xml");
outputter.output(document, fos);
fos.close();
logger.debug("File sun-jaxws.xml modified");
}
/**
* Deploys the WAR in Tomcat
* @param compositionPlanName The name of the composition
* @return the endpoint of the deployed web service or null, if the deployment failed
*/
public String deployWarTomcat(String compositionPlanName){
String response = tomcatClient.deployWar(compositionPlanName,
getWarPath(compositionPlanName));
if(response.contains("OK")){
logger.debug("WAR file deployed");
String endpoint = config.tomcatPublicAddress +
compositionPlanName +
"/service?wsdl";
return endpoint;
} else {
logger.debug("WAR file not deployed");
return null;
}
}
}