/**
* xweb project
* Created by Hamed Abdollahpour
* http://www.mobile4use.com/xweb
*/
package ir.xweb.module;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Object;
import java.lang.String;
import java.lang.System;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ir.xweb.server.Constants;
import org.apache.commons.fileupload.FileItem;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Manager {
private final static Logger logger = LoggerFactory.getLogger("Manager");
private final static String DEFAULT_PROPERTIES_PREFIX = "default.";
private final LinkedHashMap<String, Module> modules = new LinkedHashMap<String, Module>();
private ModuleParam properties;
private final ServletContext context;
private final List<ScheduledExecutorService> schedulers = new ArrayList<ScheduledExecutorService>();
public Manager(final ServletContext context) {
this.context = context;
}
public void load(final InputStream in) throws IOException {
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
load(classLoader, in);
}
public void load(final ClassLoader classLoader, final InputStream in) throws IOException {
try {
final SAXBuilder builder = new SAXBuilder();
final Document document = builder.build(in);
final Element root = document.getRootElement();
final Map<String, String> envParam = getEnvMap();
final Element envPropertiesElement = root.getChild("properties");
if(envPropertiesElement != null) {
this.properties = (ModuleParam) getElement(null, envParam, envPropertiesElement);
}
if(this.properties != null) {
for (String key:this.properties.keySet()) {
final String value = this.properties.getString(key);
if(value != null) {
envParam.put(key, value);
}
}
}
final ModuleParam defaultParam = getDefaultProperties(this.properties);
Element modulesElement = root.getChild("modules");
if(modulesElement != null) {
final List<?> moduleElements = modulesElement.getChildren("module");
for(Object o:moduleElements) {
final Element model = (Element)o;
final String name = model.getChildText("name");
final String author = model.getChildText("author");
final String className = model.getChildText("class");
final List<Validator> validators = new ArrayList<Validator>();
final Element validatorsElement = model.getChild("validators");
if(validatorsElement != null) {
final List<?> validatorElements = validatorsElement.getChildren("validator");
for(Object o2:validatorElements) {
final Element v = (Element)o2;
validators.add(new Validator(
v.getAttributeValue("param"),
v.getAttributeValue("regex"),
"true".equalsIgnoreCase(v.getAttributeValue("require"))
));
}
}
final List<Role> roles = new ArrayList<Role>();
final Element rolesElement = model.getChild("roles");
if(rolesElement != null) {
final List<?> roleElements = rolesElement.getChildren("role");
for(Object o2:roleElements) {
final Element role = (Element)o2;
final Role r = new Role(
role.getAttributeValue("param"),
role.getAttributeValue("match"),
role.getAttributeValue("accept"),
role.getAttributeValue("reject"),
role.getAttributeValue("or"),
role.getAttributeValue("and")
);
boolean add = true;
if(r.and != null) {
for(Role a:roles) {
if(a.and != null && a.and.equals(r.and)) {
if(a.ands == null) {
a.ands = new ArrayList<ModuleInfoRole>();
}
a.ands.add(r);
add = false;
break;
}
}
}
if(r.or != null) {
for(Role a:roles) {
if(a.or != null && a.or.equals(r.or)) {
if(a.ors == null) {
a.ors = new ArrayList<ModuleInfoRole>();
}
a.ors.add(r);
add = false;
break;
}
}
}
if(add) {
roles.add(r);
}
}
}
final Element propertiesElement = model.getChild("properties");
final ModuleParam properties;
if(propertiesElement != null) {
properties = (ModuleParam) getElement(defaultParam, envParam, propertiesElement);
} else {
properties = new ModuleParam();
}
try {
final Info info = new Info(name, author, validators, new ArrayList<ModuleInfoRole>(roles));
final Class<?> c = Class.forName(className, false, classLoader);
final Constructor<?> cons = c.getConstructor(Manager.class, ModuleInfo.class, ModuleParam.class);
final Module module = (Module) cons.newInstance(this, info, properties);
// Add schedules now
final Element schedulesElement = model.getChild("schedules");
if(schedulesElement != null) {
final List<?> roleElements = schedulesElement.getChildren("schedule");
for(Object o2:roleElements) {
final Element schedule = (Element)o2;
final String start = schedule.getAttributeValue("start");
final String period = schedule.getAttributeValue("period");
final String query = schedule.getAttributeValue("query");
if(start == null && period == null) {
throw new IllegalArgumentException("On of start or period should define for schedule");
}
long s = 0;
long p = 0;
if(start != null) {
s = getStart(start);
}
// system load
s += 2000;
if(period != null) {
p = getPeriod(period);
}
// we don't add schedule in module test
if(!"true".equals(System.getProperty("ir.xweb.test"))) {
addSchedule(module, query, s, p);
}
}
}
modules.put(name, module);
} catch (Exception ex) {
throw new IOException("Error in load module: " + className + " (" + name + ")", ex);
}
}
}
// now init modules with same order
for(Map.Entry<String, Module> m:modules.entrySet()) {
try {
m.getValue().init(context);
} catch (Exception ex) {
logger.error("Error to init module: " + m.getClass().getName(), ex);
}
}
} catch (JDOMException ex) {
throw new IOException(ex);
}
}
public void load(final URL url) throws IOException {
load(url.openStream());
}
public void load(final ClassLoader classLoader, final URL url) throws IOException {
load(classLoader, url.openStream());
}
public void destroy() {
for(ScheduledExecutorService s:schedulers) {
s.shutdown();
}
if(modules != null) {
for(Module m:modules.values()) {
try {
m.destroy();
} catch(Exception ex) {
logger.error("Error to destroy module: " + m ,ex);
}
}
}
}
public String getProperty(final String name) {
if(name == null) {
throw new IllegalArgumentException("null name");
}
if(this.properties != null) {
return this.properties.getString(name);
}
return null;
}
public Map<String, Module> getModules() {
return modules;
}
public Module getModule(final String name) {
return modules.get(name);
}
public <T extends Module> T getModule(final Class<T> clazz) {
//String name = clazz.getName();
for(Module m:modules.values()) {
if(clazz.isAssignableFrom(m.getClass())) {
return (T)m;
}
}
return null;
}
public <T extends Module> T getModuleOrThrow(final Class<T> clazz) {
//String name = clazz.getName();
for(Module m:modules.values()) {
if(clazz.isAssignableFrom(m.getClass())) {
return (T)m;
}
}
throw new IllegalArgumentException("Module " + clazz.getName() + " not find. But it's require");
}
private ModuleParam getDefaultProperties(final ModuleParam def) {
if(def != null) {
final ModuleParam param = new ModuleParam();
for (Map.Entry<String, Object> e:def.entrySet()) {
if(e.getKey().startsWith(DEFAULT_PROPERTIES_PREFIX)) {
final String k = e.getKey().substring(DEFAULT_PROPERTIES_PREFIX.length());
final Object v;
if(e.getValue() instanceof ModuleParam) {
v = getDefaultProperties((ModuleParam) e.getValue());
} else {
v = e.getValue();
}
param.put(k, v);
}
}
if(param.size() > 0) {
return param;
}
}
return null;
}
/**
* Get list of parametters from system
* @return
*/
private Map<String, String> getEnvMap() {
final Map<String, String> env = new HashMap<String, String>();
if(System.getenv() != null) {
env.putAll(System.getenv());
}
// system environment path
final Enumeration<Object> keys = System.getProperties().keys();
while(keys.hasMoreElements()) {
final String key = keys.nextElement().toString();
final String value = System.getProperties().get(key).toString();
if(value != null) {
env.put(key, value);
}
}
// application parameters
final Enumeration<?> initNames = context.getInitParameterNames();
while(initNames.hasMoreElements()) {
final String name = initNames.nextElement().toString();
final String value = context.getInitParameter(name);
env.put(name, value);
}
// custom items
env.put("xweb.dir", context.getRealPath("."));
String base = System.getProperty("catalina.base");
if(base == null) {
base = System.getProperty("jetty.home");
}
if(base == null) {
base = System.getProperty("user.dir");
}
env.put("xweb.base", base);
env.put("xweb.root", context.getRealPath("/"));
return env;
}
private String applyEnvironmentVariable(Map<String, String> env, final String s) {
final Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}");
final Matcher m = pattern.matcher(s);
final StringBuffer sb = new StringBuffer();
while (m.find()) {
final String text = m.group(1);
final String value = env.get(text);
m.appendReplacement(sb, value == null ? "" : value);
}
m.appendTail(sb);
return sb.toString();
}
public static Map<String, Object> getUrlParameters(final String stringQuery) throws IOException {
final Map<String, Object> params = new HashMap<String, Object>();
for (String param : stringQuery.split("&")) {
final String pair[] = param.split("=");
final String key = URLDecoder.decode(pair[0], "UTF-8");
String value = "";
if (pair.length > 1) {
value = URLDecoder.decode(pair[1], "UTF-8");
}
params.put(new String(key), new String(value));
}
return params;
}
private void addSchedule(final Module module, final String queryString, long start, long period) throws IOException {
final ModuleParam param = new ModuleParam(getUrlParameters(queryString));
try {
final Runnable runnable = new Runnable() {
@Override
public void run() {
try {
final String requestUri = Constants.MODULE_URI_PERFIX;// + "?" + Constants.MODULE_NAME_PARAMETER + "=" + queryString;
final ScheduleRequest request = new ScheduleRequest(context, param, "POST", requestUri);
final ScheduleResponse response = new ScheduleResponse();
final ScheduleChain chain = new ScheduleChain();
chain.context = context;
chain.iterator = modules.values().iterator();
chain.module = module;
chain.param = param;
chain.doFilter(request, response);
}
catch (Exception ex) {
logger.error("Error to execute schedule for: " + module.getInfo().getName() + " for: " + queryString, ex);
}
}
};
final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
if(period > 0) {
scheduler.scheduleAtFixedRate(runnable, start, period, TimeUnit.MILLISECONDS);
} else {
scheduler.schedule(runnable, start, TimeUnit.MILLISECONDS);
}
schedulers.add(scheduler);
} catch (Exception ex) {
logger.error("Error in maintenance task", ex);
}
}
private Object getElement(
final ModuleParam def,
final Map<String, String> envParams,
final Element element)
{
if(element == null) {
throw new IllegalArgumentException("null");
}
if(element.getChildren().size() == 0) {
final String value = element.getAttributeValue("value");
if(value != null) {
return value;
} else {
final String text = element.getText();
return applyEnvironmentVariable(envParams, text);
}
}
final ModuleParam param = new ModuleParam(def);
for (Object o:element.getChildren()) {
final Element e = (Element) o;
final String key = e.getAttributeValue("key");
if (key == null) {
throw new IllegalArgumentException("key attribute not found");
}
// for array type, none of children should have key attribute
boolean isArray = false;
if (e.getChildren().size() > 0) {
isArray = true;
for (Object _o:e.getChildren()) {
final Element _e = (Element) _o;
if (_e.getAttributeValue("key") != null) {
isArray = false;
break;
}
}
}
if (isArray) {
final List<Object> list = new ArrayList<Object>(e.getChildren().size());
for (Object _o:e.getChildren()) {
final Element _e = (Element) _o;
list.add(getElement(null, envParams, _e));
}
param.put(key, list);
}
else {
param.put(key, getElement(null, envParams, e));
}
}
return param;
}
private class Info implements ModuleInfo {
final String name;
final String author;
final List<Validator> validators;
final List<ModuleInfoRole> roles;
public Info(
final String name,
final String author,
final List<Validator> validators,
final List<ModuleInfoRole> roles) {
this.name = name;
this.author = author;
this.validators = validators;
this.roles = roles;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getAuthor() {
return this.author;
}
@Override
public List<ModuleInfoValidator> getValidators() {
return new ArrayList<ModuleInfoValidator>(this.validators);
}
@Override
public List<ModuleInfoRole> getRoles() {
return new ArrayList<ModuleInfoRole>(this.roles);
}
}
private class Role implements ModuleInfoRole {
String param;
String match;
String accept;
String reject;
String or;
String and;
List<ModuleInfoRole> ors;
List<ModuleInfoRole> ands;
public Role(
final String param,
final String match,
final String accept,
final String reject,
final String or,
final String and) {
this.param = param;
this.match = match;
this.accept = accept;
this.reject = reject;
this.or = or;
this.and = and;
}
@Override
public String param() {
return this.param;
}
@Override
public String match() {
return this.match;
}
@Override
public String accept() {
return this.accept;
}
@Override
public String reject() {
return this.reject;
}
@Override
public List<ModuleInfoRole> or() {
return ors;
}
@Override
public List<ModuleInfoRole> and() {
return ands;
}
}
private long getStart(final String text) {
final long now = new Date().getTime();
final Calendar c = Calendar.getInstance();
c.setTime(new Date());
Calendar c1;
c1 = dateFor("HH:mm", text);
if(c1 != null) {
c.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY));
c.set(Calendar.MINUTE, c1.get(Calendar.MINUTE));
if(c.getTime().before(new Date(now))) {
c.add(Calendar.DAY_OF_MONTH, 1);
}
return c.getTimeInMillis() - now;
}
c1 = dateFor("EEE HH:mm", text);
if(c1 != null) {
c.set(Calendar.DAY_OF_WEEK, c1.get(Calendar.DAY_OF_WEEK));
c.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY));
c.set(Calendar.MINUTE, c1.get(Calendar.MINUTE));
if(c.getTime().before(new Date(now))) {
c.add(Calendar.WEEK_OF_MONTH, 1);
}
return c.getTimeInMillis() - now;
}
c1 = dateFor("dd HH:mm", text);
if(c1 != null) {
c.set(Calendar.DAY_OF_MONTH, c1.get(Calendar.DAY_OF_MONTH));
c.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY));
c.set(Calendar.MINUTE, c1.get(Calendar.MINUTE));
if(c.getTime().before(new Date(now))) {
c.add(Calendar.MONTH, 1);
}
return c.getTimeInMillis() - now;
}
c1 = dateFor("MM-dd HH:mm", text);
if(c1 != null) {
c.set(Calendar.MONTH, c1.get(Calendar.MONTH));
c.set(Calendar.DAY_OF_MONTH, c1.get(Calendar.DAY_OF_MONTH));
c.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY));
c.set(Calendar.MINUTE, c1.get(Calendar.MINUTE));
if(c.getTime().before(new Date(now))) {
c.add(Calendar.YEAR, 1);
}
return c.getTimeInMillis() - now;
}
return 0;
}
private long getPeriod(final String text) {
try {
return (long) (Float.parseFloat(text) * 60 * 1000D);
} catch (Exception ex) {}
if(text.endsWith("hour")) {
final String t = text.substring(0, text.length() - 4).trim();
int hour = t.length() == 0 ? 1 : Integer.parseInt(t);
return hour * 60 * 60 * 1000L;
}
if(text.endsWith("week")) {
final String t = text.substring(0, text.length() - 4).trim();
int week = t.length() == 0 ? 1 : Integer.parseInt(t);
return week * 7 * 24 * 60 * 60 * 1000L;
}
// TODO: We can not handle month in this way, month should calculate (29~31),
// we need to remove the schedule and setup new one every month
if(text.endsWith("month")) {
final String t = text.substring(0, text.length() - 5).trim();
int month = t.length() == 0 ? 1 : Integer.parseInt(t);
return month * 30 * 24 * 60 * 60 * 1000L;
}
// TODO: We can not handle year in this way (365~366),
// we need to remove the schedule and setup new one every year
if(text.endsWith("year")) {
final String t = text.substring(0, text.length() - 4).trim();
int year = t.length() == 0 ? 1 : Integer.parseInt(t);
return year * 365 * 24 * 60 * 60 * 1000L;
}
return 0;
}
private Calendar dateFor(final String pattern, final String text) {
try {
final SimpleDateFormat f = new SimpleDateFormat(pattern);
final Date date = f.parse(text);
final Calendar c = Calendar.getInstance();
c.setTime(date);
return c;
} catch (Exception ex) {
return null;
}
}
public ServletContext getContext() {
return this.context;
}
private class Validator implements ModuleInfoValidator {
final String name;
final String regex;
final boolean require;
Validator(final String name, final String regex, final boolean require) {
this.name = name;
this.regex = regex;
this.require = require;
}
@Override
public String getParam() {
return this.name;
}
@Override
public String getRegex() {
return this.regex;
}
@Override
public boolean isRequire() {
return this.require;
}
}
private class ScheduleChain implements FilterChain {
Iterator<Module> iterator;
ServletContext context;
ModuleParam param;
Module module;
@Override
public void doFilter(
final ServletRequest request,
final ServletResponse response) throws IOException, ServletException
{
if(iterator.hasNext()) {
final Module next = iterator.next();
next.doFilter(
context,
(HttpServletRequest) request,
(HttpServletResponse) response,
this);
}
else {
module.process(
context,
(HttpServletRequest) request,
(HttpServletResponse) response,
param,
Collections.<String, FileItem>emptyMap());
}
}
}
}