/*
* Copyright 2002-2007 the original author or authors.
*
* 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 org.suren.autotest.web.framework.settings;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.VisitorSupport;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import org.dom4j.xpath.DefaultXPath;
import org.jaxen.SimpleNamespaceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.suren.autotest.web.framework.core.ConfigException;
import org.suren.autotest.web.framework.core.ConfigNotFoundException;
import org.suren.autotest.web.framework.core.Locator;
import org.suren.autotest.web.framework.core.LocatorAware;
import org.suren.autotest.web.framework.core.PageContext;
import org.suren.autotest.web.framework.core.PageContextAware;
import org.suren.autotest.web.framework.core.ui.AbstractElement;
import org.suren.autotest.web.framework.core.ui.Text;
import org.suren.autotest.web.framework.data.ClasspathResource;
import org.suren.autotest.web.framework.data.DataResource;
import org.suren.autotest.web.framework.data.DataSource;
import org.suren.autotest.web.framework.data.DynamicDataSource;
import org.suren.autotest.web.framework.data.FileResource;
import org.suren.autotest.web.framework.hook.ShutdownHook;
import org.suren.autotest.web.framework.page.Page;
import org.suren.autotest.web.framework.selenium.SeleniumEngine;
import org.suren.autotest.web.framework.util.BeanUtil;
import org.suren.autotest.web.framework.util.StringUtils;
import org.suren.autotest.web.framework.validation.Validation;
import org.xml.sax.SAXException;
/**
* 页面(page)以及数据配置加载
* @author suren
* @date Jul 17, 2016 9:01:51 AM
*/
public class SettingUtil implements Closeable
{
private static final Logger logger = LoggerFactory.getLogger(SettingUtil.class);
private Map<String, Page> pageMap = new HashMap<String, Page>();
private Map<String, DataSourceInfo> dataSourceMap = new HashMap<String, DataSourceInfo>();
private Map<String, DynamicDataSource> dynamicDataSourceMap;
private ApplicationContext context;
private ShutdownHook shutdownHook;
/** 系统配置路径 */
private File configFile;
private boolean closed = false;
/** 不希望被加载数据的Page集合 */
private Set<String> excludePageSet = new HashSet<String>();
/**
* 初始化bean容器,初始化上下文对象,增加JMX管理模块,增加程序关闭钩子。
*/
public SettingUtil()
{
context = SpringUtils.getApplicationContext();
if(context == null || !((AbstractApplicationContext) context).isActive())
{
context = new ClassPathXmlApplicationContext(new String[]{"classpath*:autoTestContext.xml",
"classpath*:applicationContext.xml", "classpath*:beanScope.xml"});
}
try
{
//设置页面上下文对象
Map<String, PageContextAware> pageContextAwareList =
context.getBeansOfType(PageContextAware.class);
if(pageContextAwareList != null)
{
for(PageContextAware ware : pageContextAwareList.values())
{
ware.setPageContext(new PageContext(pageMap));
}
}
//提供运行时管理功能
// IPageMXBean pageMXBean = context.getBean(IPageMXBean.class);
//
// LocateRegistry.createRegistry(5006);
// MBeanServer server = ManagementFactory.getPlatformMBeanServer();
// server.registerMBean(pageMXBean,
// new ObjectName("org.suren.autotest.web.framework:type=IPageMXBean"));
}
catch(Exception e)
{
logger.error("jmx register process error.", e);
}
shutdownHook = new ShutdownHook(this);
Runtime.getRuntime().addShutdownHook(shutdownHook);
logger.info("init process done.");
}
/**
* 从本地文件中读取
*
* @param filePath
* @throws DocumentException
* @throws IOException
* @throws SAXException
*/
public void read(String filePath) throws DocumentException, IOException, SAXException
{
File file = new File(filePath);
try(FileInputStream fis = new FileInputStream(file))
{
read(fis);
}
}
/**
* 从类路径下读取配置文件
*
* @param fileName
* @throws IOException
* @throws DocumentException xml文件解析错误
* @throws SAXException xml文件格式校验错误
*/
public void readFromClassPath(String fileName)
throws IOException, DocumentException, SAXException
{
ClassLoader classLoader = this.getClass().getClassLoader();
try(InputStream inputStream = classLoader.getResourceAsStream(fileName))
{
if(inputStream == null)
{
throw new ConfigNotFoundException("ClassPath", fileName);
}
Validation.validationFramework(inputStream); //这里会把流关闭了
}
catch (SAXException | IOException e)
{
logger.error("Framework validation process error.", e);
throw e;
}
//读取主配置文件
try(InputStream confInput = classLoader.getResourceAsStream(fileName))
{
read(confInput);
}
catch (DocumentException | IOException e)
{
logger.error(String.format("Main config [%s] parse process error.", fileName), e);
throw e;
}
}
/**
* 从操作系统路径中加载配置文件
* @param filePath
* @throws IOException
* @throws FileNotFoundException
* @throws DocumentException
* @throws SAXException
*/
public void readFromSystemPath(String filePath) throws FileNotFoundException,
IOException, DocumentException, SAXException
{
configFile = new File(filePath);
if(!configFile.isFile())
{
throw new ConfigException(String.format("Target file [%s] is not a file.", filePath));
}
else if(!filePath.endsWith(".xml"))
{
logger.warn("Target file [%s] is not end with .xml", filePath);
}
try(InputStream configInput = new FileInputStream(configFile))
{
read(configInput);
}
}
/**
* 从流中读取配置文件
*
* @param inputStream
* @throws DocumentException
* @throws IOException
* @throws SAXException
*/
public void read(InputStream inputStream) throws DocumentException, IOException, SAXException
{
Document document = new SAXReader().read(inputStream);
parse(document);
}
/**
* 从数据源中加载第一组数据,设置到所有page类中
* @see {@link #initData(int)}
*/
public void initData()
{
initData(1);
}
/**
* 从数据源中加载指定的数据组,设置到所有page类中
* @param 数据组序号(从1开始)
* @return 如果没有数据源,则返回空列表
*/
public List<DynamicDataSource> initData(int row)
{
List<DynamicDataSource> dynamicDataSourceList = new ArrayList<DynamicDataSource>();
Iterator<String> pageIterator = pageMap.keySet().iterator();
while(pageIterator.hasNext())
{
String pageKey = pageIterator.next();
if(containExcludePage(pageKey))
{
logger.warn(String.format("Page [%s] has been exclude, "
+ "ignore for init data [%s]!", pageKey, row));
continue;
}
Page page = pageMap.get(pageKey);
DynamicDataSource dynamicDataSource = initPageData(page, row);
if(dynamicDataSource != null)
{
dynamicDataSourceList.add(dynamicDataSource);
}
}
return dynamicDataSourceList;
}
/**
* 从数据源中加载指定的数据组到指定的Page类中
* @param page
* @param row 数据组序号(从1开始)
* @return
*/
public DynamicDataSource initPageData(Page page, int row)
{
String dataSourceStr = page.getDataSource();
if(StringUtils.isBlank(dataSourceStr))
{
return null;
}
final DataSourceInfo dataSourceInfo = dataSourceMap.get(dataSourceStr);
if(dataSourceInfo == null)
{
return null;
}
getDynamicDataSources();
DataSource dataSource = (DataSource) dynamicDataSourceMap.get(dataSourceInfo.getType());//context.getBean(dataSourceInfo.getType(), DataSource.class);
DataResource clzResource = new ClasspathResource(
SettingUtil.class, dataSourceInfo.getResource());
try
{
if(clzResource.getUrl() == null)
{
File file = new File(configFile.getParentFile(), dataSourceInfo.getResource());
if(!file.isFile())
{
throw new RuntimeException("Can not found datasource file : " + file.getAbsolutePath());
}
clzResource = new FileResource(file);
}
}
catch (IOException e)
{
e.printStackTrace();
}
dataSource.loadData(clzResource, row, page);
return dataSource;
}
public Collection<DynamicDataSource> getDynamicDataSources()
{
if(dynamicDataSourceMap == null)
{
dynamicDataSourceMap = context.getBeansOfType(DynamicDataSource.class);
}
if(dynamicDataSourceMap != null)
{
return dynamicDataSourceMap.values();
}
else
{
return null;
}
}
/**
* 添加Page到要排除的集合中
* @param pageCls
*/
public void addExcludePage(String pageCls)
{
excludePageSet.add(pageCls);
}
/**
* 从要排除的Page集合中移除
* @param pageCls
*/
public void removeExcludePage(String pageCls)
{
excludePageSet.remove(pageCls);
}
/**
* 是否包含在要排除的Page集合中
* @param pageCls
* @return
*/
public boolean containExcludePage(String pageCls)
{
return excludePageSet.contains(pageCls);
}
/**
* 清空要排除的Page集合
*/
public void clearExcludePage()
{
excludePageSet.clear();
}
/**
* @return 引擎对象
*/
public SeleniumEngine getEngine()
{
return context.getBean(SeleniumEngine.class);
}
/**
* 解析整个框架主配置文件
* @param document
* @throws DocumentException
* @throws IOException
* @throws SAXException 配置文件格式错误
*/
private void parse(Document doc) throws IOException, DocumentException, SAXException
{
SimpleNamespaceContext simpleNamespaceContext = new SimpleNamespaceContext();
simpleNamespaceContext.addNamespace("ns", "http://surenpi.com");
SeleniumEngine seleniumEngine = getEngine();
if(StringUtils.isBlank(seleniumEngine.getDriverStr()))
{
XPath xpath = new DefaultXPath("/ns:autotest/ns:engine");
xpath.setNamespaceContext(simpleNamespaceContext);
// engine parse progress
Element engineEle = (Element) xpath.selectSingleNode(doc);
if (engineEle == null)
{
throw new RuntimeException("can not found engine config.");
}
String driverStr = engineEle.attributeValue("driver", "chrome");
String remoteStr = engineEle.attributeValue("remote");
String timeOutStr = engineEle.attributeValue("timeout");
String fullScreenStr = engineEle.attributeValue("fullScreen", "false");
String maximizeStr = engineEle.attributeValue("maximize", "true");
String widthStr = engineEle.attributeValue("width");
String heightStr = engineEle.attributeValue("height");
try
{
seleniumEngine.setDriverStr(driverStr);
seleniumEngine.setRemoteStr(remoteStr);
try
{
seleniumEngine.setTimeout(Long.parseLong(timeOutStr));
}
catch(NumberFormatException e)
{
logger.warn(String.format("Invalid number string [%s].", timeOutStr));
seleniumEngine.setTimeout(5);
}
try
{
seleniumEngine.setWidth(Integer.parseInt(widthStr));
seleniumEngine.setHeight(Integer.parseInt(heightStr));
}
catch(NumberFormatException e)
{
logger.warn(
String.format("Invalid number width [%s] or height [%s].",
widthStr, heightStr));
}
seleniumEngine.setFullScreen(Boolean.parseBoolean(fullScreenStr));
seleniumEngine.setMaximize(Boolean.parseBoolean(maximizeStr));
seleniumEngine.init();
}
catch (NoSuchBeanDefinitionException e)
{
logger.error("Can not found bean SeleniumEngine.", e);
}
}
XPath xpath = new DefaultXPath("/ns:autotest/ns:includePage");
xpath.setNamespaceContext(simpleNamespaceContext);
@SuppressWarnings("unchecked")
List<Element> includePageList = xpath.selectNodes(doc);
if(includePageList != null && includePageList.size() > 0)
{
for(Element includePage : includePageList)
{
String pageConfig = includePage.attributeValue("pageConfig");
readFromClassPath(pageConfig);
}
}
xpath = new DefaultXPath("/ns:autotest/ns:pages");
xpath.setNamespaceContext(simpleNamespaceContext);
Element pagesEle = (Element) xpath.selectSingleNode(doc);
String pagePackage = pagesEle.attributeValue("pagePackage", "");
if(StringUtils.isNotBlank(pagePackage))
{
pagePackage = (pagePackage.trim() + ".");
}
// pages parse progress
xpath = new DefaultXPath("/ns:autotest/ns:pages/ns:page");
xpath.setNamespaceContext(simpleNamespaceContext);
@SuppressWarnings("unchecked")
List<Element> pageNodes = xpath.selectNodes(doc);
if (pageNodes != null)
{
for (Element ele : pageNodes)
{
String pageClsStr = ele.attributeValue("class");
if (pageClsStr == null)
{
logger.warn("can not found class attribute.");
continue;
}
pageClsStr = (pagePackage + pageClsStr);
String dataSrcClsStr = ele.attributeValue("dataSource");
try
{
parse(pageClsStr, dataSrcClsStr, ele);
}
catch (NoSuchBeanDefinitionException e)
{
logger.error("Page element [{}] parse error, in document [{}].", pageClsStr, doc);
throw e;
}
catch (Exception e)
{
logger.error("Page element parse error.", e);
}
}
}
//parse datasources
xpath = new DefaultXPath("/ns:autotest/ns:dataSources/ns:dataSource");
xpath.setNamespaceContext(simpleNamespaceContext);
@SuppressWarnings("unchecked")
List<Element> dataSourceNodes = xpath.selectNodes(doc);
if(dataSourceNodes != null)
{
for(Element ele : dataSourceNodes)
{
String name = ele.attributeValue("name");
String type = ele.attributeValue("type");
String resource = ele.attributeValue("resource");
dataSourceMap.put(name, new DataSourceInfo(type, resource));
}
}
}
/**
* 解析页面Page对象
*
* @param pageClsStr
* @param dataSrcClsStr
* @param ele
*/
private void parse(final String pageClsStr, String dataSrcClsStr,
Element ele) throws Exception
{
final Object pageInstance = getBean(pageClsStr);
final Class<?> pageCls = pageInstance.getClass();
String url = ele.attributeValue("url");
if (url != null)
{
BeanUtil.set(pageInstance, "url", url);
}
String paramPrefix = ele.attributeValue("paramPrefix", "param");
BeanUtil.set(pageInstance, "paramPrefix", paramPrefix);
BeanUtil.set(pageInstance, "dataSource", dataSrcClsStr);
ele.accept(new VisitorSupport()
{
@Override
public void visit(Element node)
{
if (!"field".equals(node.getName()))
{
return;
}
String fieldName = node.attributeValue("name");
String byId = node.attributeValue("byId");
String byCss = node.attributeValue("byCss");
String byName = node.attributeValue("byName");
String byXpath = node.attributeValue("byXpath");
String byLinkText = node.attributeValue("byLinkText");
String byPartialLinkText = node.attributeValue("byPartialLinkText");
String byTagName = node.attributeValue("byTagName");
String data = node.attributeValue("data");
String type = node.attributeValue("type");
String strategy = node.attributeValue("strategy");
String paramPrefix = node.attributeValue("paramPrefix", "param");
String timeOut = node.attributeValue("timeout", "0");
if (fieldName == null || "".equals(fieldName))
{
return;
}
AbstractElement ele = null;
try
{
Method getterMethod = BeanUtils.findMethod(pageCls,
"get" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1));
if (getterMethod != null)
{
ele = (AbstractElement) getterMethod
.invoke(pageInstance);
if (ele == null)
{
logger.error(String.format(
"element [%s] is null, maybe you not set autowired.",
fieldName));
return;
}
if ("input".equals(type))
{
Text text = (Text) ele;
text.setValue(data);
ele = text;
}
ele.setId(byId);
ele.setName(byName);
ele.setTagName(byTagName);
ele.setXPath(byXpath);
ele.setCSS(byCss);
ele.setLinkText(byLinkText);
ele.setPartialLinkText(byPartialLinkText);
ele.setStrategy(strategy);
ele.setParamPrefix(paramPrefix);
ele.setTimeOut(Long.parseLong(timeOut));
}
else
{
logger.error(String.format("page cls [%s], field [%s]",
pageClsStr, fieldName));
}
}
catch (IllegalAccessException e)
{
logger.error(e.getMessage(), e);
}
catch (IllegalArgumentException e)
{
logger.error(e.getMessage(), e);
}
catch (InvocationTargetException e)
{
logger.error(e.getMessage(), e);
}
catch (ClassCastException e)
{
logger.error(String.format("fieldName [%s]", fieldName), e);
}
node.accept(new FieldLocatorsVisitor(ele));
}
});
pageMap.put(pageClsStr, (Page) pageInstance);
}
/**
* 先使用类的缩写,再使用类全程来查找
* @param pageClsStr
* @return
*/
private Object getBean(String pageClsStr)
{
String beanName = convertBeanName(pageClsStr);
Object obj = null;
try
{
obj = context.getBean(beanName);
}
catch(NoSuchBeanDefinitionException e)
{
obj = context.getBean(pageClsStr);
}
return obj;
}
/**
* 把类名称转为默认的bean名称
* @param pageClsStr
* @return
*/
private String convertBeanName(final String pageClsStr)
{
String result = pageClsStr;
int index = pageClsStr.lastIndexOf(".");
if(index < 0)
{
result = pageClsStr;
}
else
{
result = pageClsStr.substring(index + 1);
}
if(result.length() > 1 && result.charAt(1) >= 'A' && result.charAt(1) <= 'Z')
{
return result;
}
return StringUtils.uncapitalize(result);
}
/**
* 元素定位器信息解析
* @author suren
* @date 2016年7月28日 上午8:18:01
*/
class FieldLocatorsVisitor extends VisitorSupport
{
private AbstractElement absEle;
public FieldLocatorsVisitor(AbstractElement element)
{
this.absEle = element;
}
@Override
public void visit(Element node)
{
if (!"locator".equals(node.getName()))
{
return;
}
String name = node.attributeValue("name");
String value = node.attributeValue("value");
String timeoutStr = node.attributeValue("timeout");
String extend = node.attributeValue("extend");
if(StringUtils.isBlank(name) || StringUtils.isBlank(value))
{
logger.error("locator has empty name or value.");
}
long timeout = 0;
if(StringUtils.isNotBlank(timeoutStr))
{
try
{
timeout = Long.parseLong(timeoutStr);
}
catch(NumberFormatException e){}
}
Map<String, Locator> beans = context.getBeansOfType(Locator.class);
Collection<Locator> locatorList = beans.values();
for(Locator locator : locatorList)
{
if(!name.equals(locator.getType()))
{
continue;
}
if(locator instanceof LocatorAware)
{
LocatorAware locatorAware = (LocatorAware) locator;
locatorAware.setValue(value);
locatorAware.setTimeout(timeout);
locatorAware.setExtend(extend);
absEle.getLocatorList().add(locator);
break;
}
}
}
}
/**
* @param name
* @return 给定名称的Page对象
*/
public Object getPage(String name)
{
return pageMap.get(name);
}
/**
* @param type
* @return 给定类型的Page对象
*/
@SuppressWarnings("unchecked")
@Deprecated
public <T> T getPage(T type)
{
return (T) getPage(type.getClass().getName());
}
/**
* @param type
* @return 给定类型的Page对象
*/
@SuppressWarnings("unchecked")
public <T> T getPage(Class<T> type)
{
return (T) getPage(type.getName());
}
class DataSourceInfo
{
private String type;
private String resource;
public DataSourceInfo(String type, String resource)
{
this.type = type;
this.resource = resource;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public String getResource()
{
return resource;
}
public void setResource(String resource)
{
this.resource = resource;
}
}
/**
* 关闭引擎
*/
@Override
public void close() throws IOException
{
if(closed)
{
return;
}
SeleniumEngine engine = context.getBean(SeleniumEngine.class);
if(engine != null)
{
engine.close();
((AbstractApplicationContext) context).destroy();
((AbstractApplicationContext) context).close();
closed = true;
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
else
{
logger.error("Can not fond seleniumEngine, resource close failed.");
}
}
/**
* @return 是否已经关闭
*/
public boolean isClosed()
{
return closed;
}
}