/* DefaultZatsEnvironment.java
Purpose:
Description:
History:
Mar 20, 2012 Created by pao
Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.zats.mimic;
import java.io.File;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.zkoss.idom.Document;
import org.zkoss.idom.Element;
import org.zkoss.idom.input.SAXBuilder;
import org.zkoss.io.Files;
import org.zkoss.zats.ZatsException;
import org.zkoss.zats.mimic.impl.ClientCtrl;
import org.zkoss.zats.mimic.impl.EmulatorClient;
import org.zkoss.zats.mimic.impl.emulator.Emulator;
import org.zkoss.zats.mimic.impl.emulator.EmulatorBuilder;
/**
* A default implementation of {@link ZatsEnvironment}
*
* @author Hawk
* @author Dennis
*/
public class DefaultZatsEnvironment implements ZatsEnvironment{
// private static Logger logger = Logger.getLogger(DefaultZatsEnvironment.class.getName());;
private List<Client> clients = new LinkedList<Client>();
private Emulator emulator ;
private String webInfPathOrUrl;
private File tmpWebInfFolder;
private String contextPath;
/**
* Create a zats context, it uses built-in config file(web.xml, zk.xml) to init the context quickly and safely. <br/>
*/
public DefaultZatsEnvironment(){
this(null,null);
}
/**
* Create a zats Environment. <br/>
* The webInfPathOrUrl is the folder of the WEB-INF to start the zats environment. <br/>
* @param webInfPathOrUrl the folder of WEB-INF, a null value means use built-in WEB-INF folder.
*/
public DefaultZatsEnvironment(String webInfPathOrUrl){
this.webInfPathOrUrl = webInfPathOrUrl;
}
/**
* Create a zats Environment. <br/>
* The webInfPathOrUrl is the folder of the WEB-INF to start the zats environment, for example "./src/test/resources/web/WEB-INF". <br/>
* The contextPath is the path of the application context, for example "/" or "/myapp". <br/>
* @param webInfPathOrUrl the folder of WEB-INF, a null value means suing built-in WEB-INF folder.
* @param contextPath the name of the application, a null value means using "/"
*/
public DefaultZatsEnvironment(String webInfPathOrUrl,String contextPath){
this.webInfPathOrUrl = webInfPathOrUrl;
this.contextPath = contextPath;
}
public void init(String resourceRoot){
if(emulator!=null) {
throw new ZatsException("already started up");
}
if(webInfPathOrUrl==null){
URL weburl = EmulatorClient.class.getResource("WEB-INF/web.xml");
if(weburl==null){
throw new IllegalStateException("built-in web.xml not found");
}
webInfPathOrUrl = weburl.toExternalForm();
//the web-info url
webInfPathOrUrl = webInfPathOrUrl.substring(0,webInfPathOrUrl.lastIndexOf('/')+1);
}
makeTmpWebInf();
EmulatorBuilder builder = new EmulatorBuilder();
builder.setWebInf(webInfPathOrUrl);
builder.setContextPath(contextPath==null?"/":contextPath);
builder.addContentRoot(resourceRoot);
emulator = builder.create();
}
/**
* In order to catch exception from zk ExecutionCleanup, we copy the WEB-INF dir
* to tmp dir while the location is given from java.io.tmpdir
* and then add one more config into zk.xml
* @param weburl
*/
private void makeTmpWebInf() {
PrintWriter writer = null;
try {
//copy whole WEB-INF dir to tmp
String os = System.getProperty("os.name").toLowerCase();
webInfPathOrUrl = os.indexOf("win") >= 0 ? webInfPathOrUrl.replace("file:/", "") :
webInfPathOrUrl.replace("file:", "");
File srcFolder = new File(webInfPathOrUrl);
tmpWebInfFolder = new File(System.getProperty("java.io.tmpdir"), "ZATS-TMP-WEB-INF");
String tmpWebInfPathOrUrl = tmpWebInfFolder.getAbsolutePath();
if (tmpWebInfFolder.exists())
Files.deleteAll(tmpWebInfFolder);
Files.copy(tmpWebInfFolder, srcFolder, Files.CP_OVERWRITE);
//insert one more config
Document zkxml = new SAXBuilder(true, false, true).build(new File(webInfPathOrUrl, "zk.xml"));
Element el = zkxml.getRootElement();
Element listener = new Element("listener");
Element listenerclass = new Element("listener-class");
listenerclass.setContent("org.zkoss.zats.mimic.exception.ZKExecutionCleanup");
listener.appendChild(listenerclass);
el.appendChild(listener);
//create new zk.xml, delete the original one
File originZKXml = new File(tmpWebInfFolder, "zk.xml");
if (!originZKXml.delete()) {
throw new ZatsException("Can't remove zk.xml under " + tmpWebInfPathOrUrl);
}
DOMSource domSource = new DOMSource(zkxml);
writer = new PrintWriter(originZKXml);
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
writer.println();
//point web info dir to the tmp one
webInfPathOrUrl = tmpWebInfPathOrUrl;
} catch (Exception e) {
throw new ZatsException(e.getMessage(), e);
} finally {
if (writer != null) {
writer.close();
}
}
}
private void deleteTmpWebInf() {
webInfPathOrUrl = null; // ensure to reset web inf path
if (tmpWebInfFolder != null && tmpWebInfFolder.exists())
Files.deleteAll(tmpWebInfFolder);
}
public void destroy() {
cleanup();
if (emulator!=null){
emulator.close();
emulator=null;
}
deleteTmpWebInf();
}
/**
* create a client.
* @return a new client
*/
public Client newClient(){
if(emulator==null){
throw new ZatsException("not initialize yet, please call init first");
}
Client client = new EmulatorClient(emulator);
((ClientCtrl)client).setDestroyListener(new ClientCtrl.DestroyListener() {
public void willDestroy(Client conv) {
clients.remove(conv);//just remove it from list, client will destory itself
}
});
clients.add(client);
return client;
}
/**
* close all clients and release resources.
*/
public void cleanup() {
//to avoid concurrent modification exception in willClose
for (Client c : clients.toArray(new Client[clients.size()])){
c.destroy();
}
}
}