interceptorIList){
+ if(interceptorIList == null || interceptorIList.size() <= 1){
+ return;
+ }
+ T newInterceptor = interceptorIList.get(interceptorIList.size() - 1);
+ Order order = newInterceptor.getClass().getDeclaredAnnotation(Order.class);
+ if(order == null){
+ return;
+ }
+ int index = interceptorIList.size() - 1;
+ for(int i = interceptorIList.size() - 2; i >= 0; i--){
+ int itemOrderInt = Ordered.LOWEST_PRECEDENCE;
+ Order itemOrder = interceptorIList.get(i).getClass().getDeclaredAnnotation(Order.class);
+ if(itemOrder != null){
+ itemOrderInt = itemOrder.value();
+ }
+ if(itemOrderInt > order.value()){
+ interceptorIList.set(index, interceptorIList.get(i));
+ index = i;
+ }else {
+ break;
+ }
+ }
+ if(index < interceptorIList.size() - 1){
+ interceptorIList.set(index, newInterceptor);
+ }
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/Bootstrap.java b/halo-core/src/main/java/org/xujin/halo/boot/Bootstrap.java
new file mode 100644
index 0000000..b017f4c
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/Bootstrap.java
@@ -0,0 +1,67 @@
+package org.xujin.halo.boot;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.xujin.halo.exception.InfraException;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 应用的核心引导启动类
+ *
+ * 负责扫描在applicationContext.xml中配置的packages. 获取到CommandExecutors, intercepters, extensions, validators等
+ * 交给各个注册器进行注册。
+ *
+ * @author xujin 2017-11-04
+ */
+public class Bootstrap {
+ @Getter
+ @Setter
+ private List packages;
+ private ClassPathScanHandler handler;
+
+ @Autowired
+ private RegisterFactory registerFactory;
+
+
+ public void init() {
+ Set> classSet = scanConfiguredPackages();
+ registerBeans(classSet);
+ }
+
+ /**
+ * @param classSet
+ */
+ private void registerBeans(Set> classSet) {
+ for (Class> targetClz : classSet) {
+ RegisterI register = registerFactory.getRegister(targetClz);
+ if (null != register) {
+ register.doRegistration(targetClz);
+ }
+ }
+
+ }
+
+ /**
+ * Scan the packages configured in Spring xml
+ *
+ * @return
+ */
+ private Set> scanConfiguredPackages() {
+ if (packages == null) throw new InfraException("Command packages is not specified");
+
+ String[] pkgs = new String[packages.size()];
+ handler = new ClassPathScanHandler(packages.toArray(pkgs));
+
+ Set> classSet = new TreeSet<>(new ClassNameComparator());
+ for (String pakName : packages) {
+ classSet.addAll(handler.getPackageAllClasses(pakName, true));
+ }
+ return classSet;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/ClassInterfaceChecker.java b/halo-core/src/main/java/org/xujin/halo/boot/ClassInterfaceChecker.java
new file mode 100644
index 0000000..4e2efc0
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/ClassInterfaceChecker.java
@@ -0,0 +1,28 @@
+package org.xujin.halo.boot;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author xujin
+ * @date 2018/3/21
+ */
+public class ClassInterfaceChecker {
+
+ public static boolean check(Class> targetClz, String expectedName){
+ //If it is not Class, just return false
+ if(targetClz.isInterface()){
+ return false;
+ }
+
+ Class[] interfaces = targetClz.getInterfaces();
+ if (ArrayUtils.isEmpty(interfaces))
+ return false;
+ for (Class intf : interfaces) {
+ String intfSimpleName = intf.getSimpleName();
+ if (StringUtils.equals(intfSimpleName, expectedName))
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/ClassNameComparator.java b/halo-core/src/main/java/org/xujin/halo/boot/ClassNameComparator.java
new file mode 100644
index 0000000..c2bc6df
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/ClassNameComparator.java
@@ -0,0 +1,14 @@
+package org.xujin.halo.boot;
+
+import java.util.Comparator;
+
+public class ClassNameComparator implements Comparator> {
+ @Override
+ public int compare(Class> o1, Class> o2) {
+ if (o1 == null)
+ return -1;
+ if (o2 == null)
+ return 1;
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/ClassPathScanHandler.java b/halo-core/src/main/java/org/xujin/halo/boot/ClassPathScanHandler.java
new file mode 100644
index 0000000..4fd863b
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/ClassPathScanHandler.java
@@ -0,0 +1,303 @@
+package org.xujin.halo.boot;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+
+import org.reflections.Reflections;
+import org.reflections.scanners.SubTypesScanner;
+import org.reflections.scanners.TypeAnnotationsScanner;
+import org.reflections.util.ConfigurationBuilder;
+
+import org.xujin.halo.logger.Logger;
+import org.xujin.halo.logger.LoggerFactory;
+
+import lombok.Getter;
+import lombok.Setter;
+
+
+/**
+ * @author xujin
+ * @since 15/7/25
+ */
+public class ClassPathScanHandler {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathScanHandler.class);
+
+ /**
+ * class file extension name.
+ */
+ private static final String CLASS_EXTENSION_NAME = ".class";
+ /**
+ * 是否排除内部类 true->是 false->否.
+ */
+ @Getter
+ @Setter
+ private boolean excludeInner = true;
+ /**
+ * 过滤规则适用情况 true—>搜索符合规则的 false->排除符合规则的.
+ */
+ @Getter
+ @Setter
+ private boolean checkInOrEx = true;
+ /**
+ * 过滤规则列表 如果是null或者空,即全部符合不过滤.
+ */
+ @Getter
+ @Setter
+ private List classFilters = null;
+ /**
+ * the reflections.
+ */
+ @Getter
+ @Setter
+ private Reflections reflections = null;
+
+ /**
+ * the classes and the packages to be scanned.
+ *
+ * @param packages packages.
+ */
+ public ClassPathScanHandler(String... packages) {
+ this.reflections = new Reflections(new ConfigurationBuilder().
+ forPackages(packages).
+ addScanners(new TypeAnnotationsScanner(), new SubTypesScanner())//.addUrls(urlList)
+ );
+ }
+
+ /**
+ * excludeInner:是否排除内部类 true->是 false->否
.
+ * checkInOrEx:过滤规则适用情况 true—>搜索符合规则的 false->排除符合规则的
+ * classFilters:自定义过滤规则,如果是null或者空,即全部符合不过滤
+ *
+ * @param excludeInner whether exclude the inner class.
+ * @param checkInOrEx whether exclude the rule checking.
+ * @param classFilters the customized the classes to be filtered.
+ */
+ public ClassPathScanHandler(Boolean excludeInner, Boolean checkInOrEx,
+ List classFilters) {
+ this.excludeInner = excludeInner;
+ this.checkInOrEx = checkInOrEx;
+ this.classFilters = classFilters;
+
+ }
+
+ /**
+ * get all the classes with annotation.
+ *
+ * @param annotation the specific annotation.
+ * @param honorInherited honorInherited
+ * @return the set of the classes.
+ */
+ public Set> getAllClassesWithAnnotation(Class extends Annotation> annotation, boolean honorInherited) {
+ return reflections.getTypesAnnotatedWith(annotation, honorInherited);
+ }
+
+ /**
+ * get all the sub classes with the specific parent class.
+ *
+ * @param parent the parent class.
+ * @param sub class's type.
+ * @return the set of the sub classes found.
+ */
+ public Set> getAllSubClassesByParent(Class parent) {
+ return reflections.getSubTypesOf(parent);
+ }
+
+ /**
+ * scan the package.
+ *
+ * @param basePackage the basic class package's string.
+ * @param recursive whether to search recursive.
+ * @return Set of the found classes.
+ */
+ public Set> getPackageAllClasses(String basePackage, boolean recursive) {
+ if (basePackage == null)
+ return new HashSet<>();
+ Set> classes = new LinkedHashSet>();
+ String packageName = basePackage;
+ if (packageName.endsWith(".")) {
+ packageName = packageName.substring(0, packageName.lastIndexOf('.'));
+ }
+ String package2Path = packageName.replace('.', '/');
+
+ Enumeration dirs;
+ try {
+ dirs = Thread.currentThread().getContextClassLoader().getResources(package2Path);
+ while (dirs.hasMoreElements()) {
+ URL url = dirs.nextElement();
+ String protocol = url.getProtocol();
+ if ("file".equals(protocol)) {
+ LOGGER.debug("扫描file类型的class文件....");
+ String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
+ doScanPackageClassesByFile(classes, packageName, filePath, recursive);
+ } else if ("jar".equals(protocol)) {
+ LOGGER.debug("扫描jar文件中的类....");
+ doScanPackageClassesByJar(packageName, url, recursive, classes);
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.error("IOException error:");
+ }
+
+ TreeSet> sortedClasses = new TreeSet<>(new ClassNameComparator());
+ sortedClasses.addAll(classes);
+ return sortedClasses;
+ }
+
+ /**
+ * 以jar的方式扫描包下的所有Class文件
.
+ *
+ * @param basePackage eg:michael.utils.
+ * @param url the url.
+ * @param recursive whether to search recursive.
+ * @param classes set of the found classes.
+ */
+ private void doScanPackageClassesByJar(String basePackage, URL url, final boolean recursive, Set> classes) {
+ String package2Path = basePackage.replace('.', '/');
+ JarFile jar;
+ try {
+ jar = ((JarURLConnection) url.openConnection()).getJarFile();
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (!name.startsWith(package2Path) || entry.isDirectory()) {
+ continue;
+ }
+ // 判断是否递归搜索子包
+ if (!recursive && name.lastIndexOf('/') != package2Path.length()) {
+ continue;
+ }
+ // 判断是否过滤 inner class
+ if (this.excludeInner && name.indexOf('$') != -1) {
+ LOGGER.debug("exclude inner class with name:" + name);
+ continue;
+ }
+ String classSimpleName = name.substring(name.lastIndexOf('/') + 1);
+ // 判定是否符合过滤条件
+ if (this.filterClassName(classSimpleName)) {
+ String className = name.replace('/', '.');
+ className = className.substring(0, className.length() - 6);
+ try {
+ classes.add(Thread.currentThread().getContextClassLoader().loadClass(className));
+ } catch (ClassNotFoundException e) {
+ LOGGER.error("Class.forName error:URL is ===>" + url.getPath());
+ }
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.error("IOException error:URL is ===>" + url.getPath());
+ } catch (Throwable e) {
+ LOGGER.error("ScanPackageClassesByJar error:URL is ===>" + url.getPath());
+ }
+ }
+
+ /**
+ * 以文件的方式扫描包下的所有Class文件.
+ *
+ * @param packageName the package name for scanning.
+ * @param packagePath the package path for scanning.
+ * @param recursive whether to search recursive.
+ * @param classes set of the found classes.
+ */
+ private void doScanPackageClassesByFile(
+ Set> classes, String packageName, String packagePath, final boolean recursive) {
+ File dir = new File(packagePath);
+ if (!dir.exists() || !dir.isDirectory()) {
+ return;
+ }
+ File[] files = dir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return filterClassFileByCustomization(pathname, recursive);
+ }
+ });
+
+ if (null == files || files.length == 0) {
+ return;
+ }
+ for (File file : files) {
+ if (file.isDirectory()) {
+ doScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath(), recursive);
+ } else {
+ String className = file.getName().substring(0,
+ file.getName().length() - CLASS_EXTENSION_NAME.length());
+ try {
+ classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
+ } catch (ClassNotFoundException e) {
+ LOGGER.error("IOException error:");
+ }
+ }
+ }
+ }
+
+ /**
+ * filter the class file from the customized rules.
+ *
+ * @param file the class file to be filtered.
+ * @param recursive whether search recursive.
+ * @return true: match, false: not match.
+ */
+ private boolean filterClassFileByCustomization(@Nonnull File file, boolean recursive) {
+ if (file.isDirectory()) {
+ return recursive;
+ }
+ String filename = file.getName();
+ if (excludeInner && filename.indexOf('$') != -1) {
+ LOGGER.debug("exclude inner class with name:" + filename);
+ return false;
+ }
+ return filterClassName(filename);
+ }
+
+ /**
+ * 根据过滤规则判断类名.
+ *
+ * @param className the class name.
+ * @return whether to be filtered.
+ */
+ private boolean filterClassName(String className) {
+ if (!className.endsWith(CLASS_EXTENSION_NAME)) {
+ return false;
+ }
+ if (null == this.classFilters || this.classFilters.isEmpty()) {
+ return true;
+ }
+ String tmpName = className.substring(0, className.length() - 6);
+ boolean flag = false;
+ for (String str : classFilters) {
+ flag = matchInnerClassname(tmpName, str);
+ if (flag) break;
+ }
+ return (checkInOrEx && flag) || (!checkInOrEx && !flag);
+ }
+
+ /**
+ * check the className whether match the inner class's rule.
+ *
+ * @param className the inner class name.
+ * @param filterString the filter string.
+ * @return true or false.
+ */
+ private boolean matchInnerClassname(String className, String filterString) {
+ String reg = "^" + filterString.replace("*", ".*") + "$";
+ Pattern p = Pattern.compile(reg);
+ return p.matcher(className).find();
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/CommandRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/CommandRegister.java
new file mode 100644
index 0000000..f820a01
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/CommandRegister.java
@@ -0,0 +1,83 @@
+package org.xujin.halo.boot;
+
+import org.xujin.halo.command.CommandHub;
+import org.xujin.halo.command.CommandInterceptorI;
+import org.xujin.halo.command.CommandInvocation;
+import org.xujin.halo.common.CoreConstant;
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.exception.InfraException;
+import com.google.common.collect.Iterables;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * CommandRegister
+ *
+ * @author xujin 2017-11-04
+ */
+
+@SuppressWarnings({ "rawtypes", "unchecked" })
+@Component
+public class CommandRegister extends AbstractRegister implements ApplicationContextAware {
+
+ @Autowired
+ private CommandHub commandHub;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ Class extends Command> commandClz = getCommandFromExecutor(targetClz);
+ CommandInvocation commandInvocation = new CommandInvocation();
+
+ commandInvocation.setCommandExecutor(getBean(targetClz));
+ commandInvocation.setPreInterceptors(collectInterceptors(commandClz, true));
+ commandInvocation.setPostInterceptors(collectInterceptors(commandClz, false));
+ commandHub.getCommandRepository().put(commandClz, commandInvocation);
+ }
+
+ private Class extends Command> getCommandFromExecutor(Class> commandExecutorClz) {
+ Method[] methods = commandExecutorClz.getDeclaredMethods();
+ for (Method method : methods) {
+ Class>[] exeParams = method.getParameterTypes();
+ /**
+ * This is for return right response type on exception scenarios
+ */
+ if (CoreConstant.EXE_METHOD.equals(method.getName()) && exeParams.length == 1
+ && Command.class.isAssignableFrom(exeParams[0]) && !method.isBridge()) {
+ commandHub.getResponseRepository().put(exeParams[0], method.getReturnType());
+ return (Class extends Command>) exeParams[0];
+ }
+ }
+ throw new InfraException("Command param in " + commandExecutorClz + " " + CoreConstant.EXE_METHOD
+ + "() is not detected");
+ }
+
+ private Iterable collectInterceptors(Class extends Command> commandClass, boolean pre) {
+ /**
+ * add 通用的Interceptors
+ */
+ Iterable commandItr = Iterables.concat((pre ? commandHub.getGlobalPreInterceptors() : commandHub.getGlobalPostInterceptors()));
+ /**
+ * add command自己专属的Interceptors
+ */
+ Iterables.concat(commandItr, (pre ? commandHub.getPreInterceptors() : commandHub.getPostInterceptors()).get(commandClass));
+ /**
+ * add parents的Interceptors
+ */
+ Class> superClass = commandClass.getSuperclass();
+ while (Command.class.isAssignableFrom(superClass)) {
+ Iterables.concat(commandItr, (pre ? commandHub.getPreInterceptors() : commandHub.getPostInterceptors()).get(commandClass));
+ superClass = superClass.getSuperclass();
+ }
+ return commandItr;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/ComponentExecutor.java b/halo-core/src/main/java/org/xujin/halo/boot/ComponentExecutor.java
new file mode 100644
index 0000000..455b604
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/ComponentExecutor.java
@@ -0,0 +1,17 @@
+package org.xujin.halo.boot;
+
+import java.util.function.Function;
+
+/**
+ * @author xujin
+ * @date 2018/4/21
+ */
+public abstract class ComponentExecutor {
+
+ public R execute(Class targetClz, Function exeFunction) {
+ C component = locateComponent(targetClz);
+ return exeFunction.apply(component);
+ }
+
+ protected abstract C locateComponent(Class targetClz);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/EventRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/EventRegister.java
new file mode 100644
index 0000000..69564c8
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/EventRegister.java
@@ -0,0 +1,57 @@
+package org.xujin.halo.boot;
+
+import org.xujin.halo.common.CoreConstant;
+import org.xujin.halo.dto.event.Event;
+import org.xujin.halo.event.EventHandlerI;
+import org.xujin.halo.event.EventHub;
+import org.xujin.halo.exception.InfraException;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * EventRegister
+ *
+ * @author xujin
+ * @date 2018/3/20
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+@Component
+public class EventRegister extends AbstractRegister implements ApplicationContextAware {
+
+ @Autowired
+ private EventHub eventHub;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ Class extends Event> eventClz = getEventFromExecutor(targetClz);
+ EventHandlerI executor = getBean(targetClz);
+ eventHub.register(eventClz, executor);
+ }
+
+ private Class extends Event> getEventFromExecutor(Class> eventExecutorClz) {
+ Method[] methods = eventExecutorClz.getDeclaredMethods();
+ for (Method method : methods) {
+ Class>[] exeParams = method.getParameterTypes();
+ /**
+ * This is for return right response type on exception scenarios
+ */
+ if (CoreConstant.EXE_METHOD.equals(method.getName()) && exeParams.length == 1
+ && Event.class.isAssignableFrom(exeParams[0]) && !method.isBridge()) {
+ eventHub.getResponseRepository().put(eventExecutorClz, method.getReturnType());
+ return (Class extends Event>) exeParams[0];
+ }
+ }
+ throw new InfraException("Event param in " + eventExecutorClz + " " + CoreConstant.EXE_METHOD
+ + "() is not detected");
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/ExtensionRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/ExtensionRegister.java
new file mode 100644
index 0000000..9daf3b6
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/ExtensionRegister.java
@@ -0,0 +1,63 @@
+package org.xujin.halo.boot;
+
+import org.xujin.halo.common.CoreConstant;
+import org.xujin.halo.exception.InfraException;
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.extension.ExtensionCoordinate;
+import org.xujin.halo.extension.ExtensionPointI;
+import org.xujin.halo.extension.ExtensionRepository;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * ExtensionRegister
+ * @author xujin
+ */
+@Component
+public class ExtensionRegister implements RegisterI, ApplicationContextAware{
+
+ @Autowired
+ private ExtensionRepository extensionRepository;
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ ExtensionPointI extension = (ExtensionPointI) applicationContext.getBean(targetClz);
+ Extension extensionAnn = targetClz.getDeclaredAnnotation(Extension.class);
+ String extensionPoint = calculateExtensionPoint(targetClz);
+ ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(extensionPoint, extensionAnn.bizCode(), extensionAnn.tenantId());
+ ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extension);
+ if (preVal != null) {
+ throw new InfraException("Duplicate registration is not allowed for :"+extensionCoordinate);
+ }
+ }
+
+ /**
+ * @param targetClz
+ * @return
+ */
+ private String calculateExtensionPoint(Class> targetClz) {
+ Class[] interfaces = targetClz.getInterfaces();
+ if (ArrayUtils.isEmpty(interfaces))
+ throw new InfraException("Please assign a extension point interface for "+targetClz);
+ for (Class intf : interfaces) {
+ String extensionPoint = intf.getSimpleName();
+ if (StringUtils.contains(extensionPoint, CoreConstant.EXTENSION_EXTPT_NAMING))
+ return extensionPoint;
+ }
+ throw new InfraException("Your name of ExtensionPoint for "+targetClz+" is not valid, must be end of "+CoreConstant.EXTENSION_EXTPT_NAMING);
+ }
+
+
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/PlainRuleRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/PlainRuleRegister.java
new file mode 100644
index 0000000..df1e921
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/PlainRuleRegister.java
@@ -0,0 +1,34 @@
+package org.xujin.halo.boot;
+
+import org.xujin.halo.rule.PlainRuleRepository;
+import org.xujin.halo.rule.RuleI;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Plain Rule is Rule Component without ExtensionPoint
+ * @author xujin
+ * @date 2018/6/21
+ */
+@Component
+public class PlainRuleRegister implements RegisterI, ApplicationContextAware {
+
+ @Autowired
+ private PlainRuleRepository plainRuleRepository;
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ RuleI plainRule = (RuleI) applicationContext.getBean(targetClz);
+ plainRuleRepository.getPlainRules().put(plainRule.getClass(), plainRule);
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/PlainValidatorRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/PlainValidatorRegister.java
new file mode 100644
index 0000000..3ff05e8
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/PlainValidatorRegister.java
@@ -0,0 +1,33 @@
+package org.xujin.halo.boot;
+
+import org.xujin.halo.validator.PlainValidatorRepository;
+import org.xujin.halo.validator.ValidatorI;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Plain Validator is Validator Component without ExtensionPoint
+ * @author xujin
+ */
+@Component
+public class PlainValidatorRegister implements RegisterI, ApplicationContextAware {
+
+ @Autowired
+ private PlainValidatorRepository plainValidatorRepository;
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ ValidatorI plainValidator= (ValidatorI) applicationContext.getBean(targetClz);
+ plainValidatorRepository.getPlainValidators().put(plainValidator.getClass(), plainValidator);
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/PostInterceptorRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/PostInterceptorRegister.java
new file mode 100644
index 0000000..e9806aa
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/PostInterceptorRegister.java
@@ -0,0 +1,48 @@
+
+package org.xujin.halo.boot;
+
+import org.xujin.halo.command.CommandHub;
+import org.xujin.halo.command.CommandInterceptorI;
+import org.xujin.halo.command.PostInterceptor;
+import org.xujin.halo.dto.Command;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * PostInterceptorRegister
+ * @author xujin
+ */
+@Component
+public class PostInterceptorRegister extends AbstractRegister implements ApplicationContextAware{
+
+ @Autowired
+ private CommandHub commandHub;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ CommandInterceptorI commandInterceptor = getBean(targetClz);
+ PostInterceptor postInterceptorAnn = targetClz.getDeclaredAnnotation(PostInterceptor.class);
+ Class extends Command>[] supportClasses = postInterceptorAnn.commands();
+ registerInterceptor(supportClasses, commandInterceptor);
+ }
+
+ private void registerInterceptor(Class extends Command>[] supportClasses, CommandInterceptorI commandInterceptor) {
+ if (null == supportClasses || supportClasses.length == 0) {
+ commandHub.getGlobalPostInterceptors().add(commandInterceptor);
+ order(commandHub.getGlobalPostInterceptors());
+ return;
+ }
+ for (Class extends Command> supportClass : supportClasses) {
+ commandHub.getPostInterceptors().put(supportClass, commandInterceptor);
+ order(commandHub.getPostInterceptors().get(supportClass));
+ }
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/PreInterceptorRegister.java b/halo-core/src/main/java/org/xujin/halo/boot/PreInterceptorRegister.java
new file mode 100644
index 0000000..c82558a
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/PreInterceptorRegister.java
@@ -0,0 +1,48 @@
+
+package org.xujin.halo.boot;
+
+import org.xujin.halo.command.CommandHub;
+import org.xujin.halo.command.CommandInterceptorI;
+import org.xujin.halo.command.PreInterceptor;
+import org.xujin.halo.dto.Command;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * PreInterceptorRegister
+ * @author xujin
+ */
+@Component
+public class PreInterceptorRegister extends AbstractRegister implements ApplicationContextAware{
+
+ @Autowired
+ private CommandHub commandHub;
+
+ @Override
+ public void doRegistration(Class> targetClz) {
+ CommandInterceptorI commandInterceptor = getBean(targetClz);
+ PreInterceptor preInterceptorAnn = targetClz.getDeclaredAnnotation(PreInterceptor.class);
+ Class extends Command>[] supportClasses = preInterceptorAnn.commands();
+ registerInterceptor(supportClasses, commandInterceptor);
+ }
+
+ private void registerInterceptor(Class extends Command>[] supportClasses, CommandInterceptorI commandInterceptor) {
+ if (null == supportClasses || supportClasses.length == 0) {
+ commandHub.getGlobalPreInterceptors().add(commandInterceptor);
+ order(commandHub.getGlobalPreInterceptors());
+ return;
+ }
+ for (Class extends Command> supportClass : supportClasses) {
+ commandHub.getPreInterceptors().put(supportClass, commandInterceptor);
+ order(commandHub.getPreInterceptors().get(supportClass));
+ }
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/RegisterFactory.java b/halo-core/src/main/java/org/xujin/halo/boot/RegisterFactory.java
new file mode 100644
index 0000000..20ebbab
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/RegisterFactory.java
@@ -0,0 +1,88 @@
+
+package org.xujin.halo.boot;
+
+import org.xujin.halo.command.Command;
+import org.xujin.halo.command.PostInterceptor;
+import org.xujin.halo.command.PreInterceptor;
+import org.xujin.halo.common.CoreConstant;
+import org.xujin.halo.event.EventHandler;
+import org.xujin.halo.exception.InfraException;
+import org.xujin.halo.extension.Extension;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * RegisterFactory
+ *
+ * @author xujin
+ */
+@Component
+public class RegisterFactory{
+
+ @Autowired
+ private PreInterceptorRegister preInterceptorRegister;
+ @Autowired
+ private PostInterceptorRegister postInterceptorRegister;
+ @Autowired
+ private CommandRegister commandRegister;
+ @Autowired
+ private ExtensionRegister extensionRegister;
+ @Autowired
+ private EventRegister eventRegister;
+ @Autowired
+ private PlainValidatorRegister plainValidatorRegister;
+ @Autowired
+ private PlainRuleRegister plainRuleRegister;
+
+ public RegisterI getRegister(Class> targetClz) {
+ PreInterceptor preInterceptorAnn = targetClz.getDeclaredAnnotation(PreInterceptor.class);
+ if (preInterceptorAnn != null) {
+ return preInterceptorRegister;
+ }
+ PostInterceptor postInterceptorAnn = targetClz.getDeclaredAnnotation(PostInterceptor.class);
+ if (postInterceptorAnn != null) {
+ return postInterceptorRegister;
+ }
+ Command commandAnn = targetClz.getDeclaredAnnotation(Command.class);
+ if (commandAnn != null) {
+ return commandRegister;
+ }
+ Extension extensionAnn = targetClz.getDeclaredAnnotation(Extension.class);
+ if (extensionAnn != null) {
+ return extensionRegister;
+ }
+ if (isPlainValidator(targetClz)) {
+ return plainValidatorRegister;
+ }
+ if (isPlainRule(targetClz)) {
+ return plainRuleRegister;
+ }
+ EventHandler eventHandlerAnn = targetClz.getDeclaredAnnotation(EventHandler.class);
+ if (eventHandlerAnn != null) {
+ return eventRegister;
+ }
+ return null;
+ }
+
+ private boolean isPlainRule(Class> targetClz) {
+ if (ClassInterfaceChecker.check(targetClz, CoreConstant.RULEI_CLASS) && makeSureItsNotExtensionPoint(targetClz)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isPlainValidator(Class> targetClz) {
+ if (ClassInterfaceChecker.check(targetClz, CoreConstant.VALIDATORI_CLASS) && makeSureItsNotExtensionPoint(targetClz)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean makeSureItsNotExtensionPoint(Class> targetClz) {
+ if (ClassInterfaceChecker.check(targetClz, CoreConstant.EXTENSIONPOINT_CLASS)) {
+ throw new InfraException(
+ "Please add @Extension for " + targetClz.getSimpleName() + " since it's a ExtensionPoint");
+ }
+ return true;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/boot/RegisterI.java b/halo-core/src/main/java/org/xujin/halo/boot/RegisterI.java
new file mode 100644
index 0000000..8b71eeb
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/boot/RegisterI.java
@@ -0,0 +1,11 @@
+
+package org.xujin.halo.boot;
+
+
+/**
+ * Register Interface
+ * @author xujin
+ */
+public interface RegisterI {
+ public void doRegistration(Class> targetClz);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/Command.java b/halo-core/src/main/java/org/xujin/halo/command/Command.java
new file mode 100644
index 0000000..5e90063
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/Command.java
@@ -0,0 +1,17 @@
+package org.xujin.halo.command;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Component
+public @interface Command {
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/CommandBus.java b/halo-core/src/main/java/org/xujin/halo/command/CommandBus.java
new file mode 100644
index 0000000..9cc10dc
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/CommandBus.java
@@ -0,0 +1,67 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.exception.BasicErrorCode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import org.xujin.halo.context.TenantContext;
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.dto.Response;
+import org.xujin.halo.exception.HaloException;
+import org.xujin.halo.exception.ErrorCodeI;
+import org.xujin.halo.exception.InfraException;
+import org.xujin.halo.logger.Logger;
+import org.xujin.halo.logger.LoggerFactory;
+
+/**
+ * Just send Command to CommandBus,
+ *
+ * @author xujin 2017年10月24日 上午12:47:18
+ */
+@Component
+public class CommandBus implements CommandBusI{
+
+ Logger logger = LoggerFactory.getLogger(CommandBus.class);
+
+ @Autowired
+ private CommandHub commandHub;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Response send(Command cmd) {
+ Response response = null;
+ try {
+ //从commandHub中获取到对应的命令去调用命令的execute方法
+ response = commandHub.getCommandInvocation(cmd.getClass()).invoke(cmd);
+ }
+ catch (Exception exception) {
+ //统一的Command异常处理器
+ response = handleException(cmd, response, exception);
+ }
+ finally {
+ //Clean up context
+ TenantContext.remove();
+ }
+ return response;
+ }
+
+ private Response handleException(Command cmd, Response response, Exception exception) {
+ logger.error(exception.getMessage(), exception);
+ Class responseClz = commandHub.getResponseRepository().get(cmd.getClass());
+ try {
+ response = (Response) responseClz.newInstance();
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ throw new InfraException(e.getMessage());
+ }
+ if (exception instanceof HaloException) {
+ ErrorCodeI errCode = ((HaloException) exception).getErrCode();
+ response.setErrCode(errCode.getErrCode());
+ }
+ else {
+ response.setErrCode(BasicErrorCode.SYS_ERROR.getErrCode());
+ }
+ response.setErrMessage(exception.getMessage());
+ return response;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/CommandBusI.java b/halo-core/src/main/java/org/xujin/halo/command/CommandBusI.java
new file mode 100644
index 0000000..2015d42
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/CommandBusI.java
@@ -0,0 +1,21 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.dto.Response;
+
+/**
+ *
+ * CommandBus
+ *
+ * @author xujin
+ */
+public interface CommandBusI {
+
+ /**
+ * Send command to CommandBus, then the command will be executed by CommandExecutor
+ *
+ * @param Command or Query
+ * @return Response
+ */
+ public Response send(Command cmd);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/CommandExecutorI.java b/halo-core/src/main/java/org/xujin/halo/command/CommandExecutorI.java
new file mode 100644
index 0000000..e804208
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/CommandExecutorI.java
@@ -0,0 +1,15 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.dto.Response;
+
+/**
+ *
+ * CommandExecutorI
+ *
+ * @author xujin
+ */
+public interface CommandExecutorI {
+
+ public R execute(C cmd);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/CommandHub.java b/halo-core/src/main/java/org/xujin/halo/command/CommandHub.java
new file mode 100644
index 0000000..e9feca9
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/CommandHub.java
@@ -0,0 +1,53 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.exception.InfraException;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Command Hub holds all the important information about Command
+ *
+ * @author xujin
+ */
+@SuppressWarnings("rawtypes")
+@Component
+public class CommandHub{
+
+ @Getter
+ @Setter
+ private ListMultimap preInterceptors = LinkedListMultimap.create();
+ @Getter
+ @Setter
+ private ListMultimap postInterceptors = LinkedListMultimap.create();
+
+ @Getter
+ @Setter
+ //全局通用的PreInterceptors
+ private List globalPreInterceptors = new ArrayList<>();
+ @Getter
+ @Setter
+ //全局通用的PostInterceptors
+ private List globalPostInterceptors = new ArrayList<>();
+ @Getter
+ @Setter
+ private Map commandRepository = new HashMap<>();
+
+ @Getter
+ private Map responseRepository = new HashMap<>();
+
+ public CommandInvocation getCommandInvocation(Class cmdClass) {
+ CommandInvocation commandInvocation = commandRepository.get(cmdClass);
+ if (commandRepository.get(cmdClass) == null){
+ throw new InfraException(cmdClass + " is not registered in CommandHub, please register first");
+ }
+ return commandInvocation;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/CommandInterceptorI.java b/halo-core/src/main/java/org/xujin/halo/command/CommandInterceptorI.java
new file mode 100644
index 0000000..6700ea5
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/CommandInterceptorI.java
@@ -0,0 +1,26 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.dto.Response;
+
+/**
+ * Interceptor will do AOP processing before or after Command Execution
+ *
+ * @author xujin
+ */
+public interface CommandInterceptorI {
+
+ /**
+ * Pre-processing before command execution
+ * @param command
+ */
+ default public void preIntercept(Command command){};
+
+ /**
+ * Post-processing after command execution
+ * @param command
+ * @param response, Note that response could be null, check it before use
+ */
+ default public void postIntercept(Command command, Response response){};
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/CommandInvocation.java b/halo-core/src/main/java/org/xujin/halo/command/CommandInvocation.java
new file mode 100644
index 0000000..f728c44
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/CommandInvocation.java
@@ -0,0 +1,55 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.dto.Response;
+import com.google.common.collect.FluentIterable;
+import lombok.Setter;
+
+import java.util.List;
+
+public class CommandInvocation {
+
+ @Setter
+ private CommandExecutorI commandExecutor;
+ @Setter
+ private Iterable preInterceptors;
+ @Setter
+ private Iterable postInterceptors;
+
+ public CommandInvocation() {
+
+ }
+
+ public CommandInvocation(CommandExecutorI commandExecutor, List preInterceptors,
+ List postInterceptors){
+ this.commandExecutor = commandExecutor;
+ this.preInterceptors = preInterceptors;
+ this.postInterceptors = postInterceptors;
+ }
+
+ public R invoke(C command) {
+ R response = null;
+ try {
+ preIntercept(command);
+ response = commandExecutor.execute(command);
+ response.setSuccess(true);
+ }
+ finally {
+ //make sure post interceptors performs even though exception happens
+ postIntercept(command, response);
+ }
+ return response;
+ }
+
+ private void postIntercept(C command, R response) {
+ for (CommandInterceptorI postInterceptor : FluentIterable.from(postInterceptors).toSet()) {
+ postInterceptor.postIntercept(command, response);
+ }
+ }
+
+ private void preIntercept(C command) {
+ for (CommandInterceptorI preInterceptor : FluentIterable.from(preInterceptors).toSet()) {
+ preInterceptor.preIntercept(command);
+ }
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/PostInterceptor.java b/halo-core/src/main/java/org/xujin/halo/command/PostInterceptor.java
new file mode 100644
index 0000000..789847e
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/PostInterceptor.java
@@ -0,0 +1,15 @@
+package org.xujin.halo.command;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Component
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PostInterceptor {
+
+ Class extends org.xujin.halo.dto.Command>[] commands() default {};
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/PreInterceptor.java b/halo-core/src/main/java/org/xujin/halo/command/PreInterceptor.java
new file mode 100644
index 0000000..f1cf85f
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/PreInterceptor.java
@@ -0,0 +1,15 @@
+package org.xujin.halo.command;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Component
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PreInterceptor {
+
+ Class extends org.xujin.halo.dto.Command>[] commands() default {};
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/command/QueryExecutorI.java b/halo-core/src/main/java/org/xujin/halo/command/QueryExecutorI.java
new file mode 100644
index 0000000..66a37fd
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/command/QueryExecutorI.java
@@ -0,0 +1,8 @@
+package org.xujin.halo.command;
+
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.dto.Response;
+
+public interface QueryExecutorI extends CommandExecutorI{
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/common/ApplicationContextHelper.java b/halo-core/src/main/java/org/xujin/halo/common/ApplicationContextHelper.java
new file mode 100644
index 0000000..410d179
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/common/ApplicationContextHelper.java
@@ -0,0 +1,27 @@
+package org.xujin.halo.common;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * ApplicationContextHelper
+ *
+ * @author xujin
+ * @date 2018-01-07 12:30 PM
+ */
+@Component
+public class ApplicationContextHelper implements ApplicationContextAware{
+
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ ApplicationContextHelper.applicationContext = applicationContext;
+ }
+
+ public static Object getBean(Class claz){
+ return ApplicationContextHelper.applicationContext.getBean(claz);
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/common/CoreConstant.java b/halo-core/src/main/java/org/xujin/halo/common/CoreConstant.java
new file mode 100644
index 0000000..1fd2d1b
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/common/CoreConstant.java
@@ -0,0 +1,22 @@
+
+package org.xujin.halo.common;
+
+/**
+ * CoreConstant
+ * @author xujin
+ */
+public class CoreConstant {
+
+ public final static String DEFAULT_BIZ_CODE = "$defaultBizCode$";
+ public final static String DEFAULT_TENANT_ID = "$defaultTenantId$";
+
+ public final static String EXTENSION_EXTPT_NAMING = "ExtPt";
+ public final static String VALIDATOR_NAMING = "Validator";
+ public final static String RULE_NAMING = "Rule";
+
+ public final static String EXTENSIONPOINT_CLASS = "ExtensionPointI";
+ public final static String VALIDATORI_CLASS = "ValidatorI";
+ public final static String RULEI_CLASS = "RuleI";
+
+ public final static String EXE_METHOD = "execute";
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/common/DefaultBizCode.java b/halo-core/src/main/java/org/xujin/halo/common/DefaultBizCode.java
new file mode 100644
index 0000000..08d895f
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/common/DefaultBizCode.java
@@ -0,0 +1,13 @@
+
+package org.xujin.halo.common;
+
+/**
+ * For App BizCode, please define it in your own App, this is for Framework only
+ * @author xujin
+ */
+public class DefaultBizCode {
+
+ public final static String DEFAULT_BIZ_CODE = "$defaultBizCode$";
+
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/common/LocalAddress.java b/halo-core/src/main/java/org/xujin/halo/common/LocalAddress.java
new file mode 100644
index 0000000..11ffb13
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/common/LocalAddress.java
@@ -0,0 +1,21 @@
+package org.xujin.halo.common;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 获取本机地址
+ * @author xujin
+ * @date 2018/02/07
+ */
+public class LocalAddress {
+ public static String IP = "";
+
+ static {
+ try {
+ IP = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/context/PvgContext.java b/halo-core/src/main/java/org/xujin/halo/context/PvgContext.java
new file mode 100644
index 0000000..c015509
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/context/PvgContext.java
@@ -0,0 +1,65 @@
+package org.xujin.halo.context;
+
+import org.xujin.halo.exception.Preconditions;
+
+/**
+ * Pvg Context
+ *
+ * Created by xujin on 2018/4/9.
+ */
+public class PvgContext {
+
+ private static ThreadLocal pvgContext = new ThreadLocal<>();
+
+ private static class Pvg {
+ private String crmUserId;
+ private String roleName;
+ private String orgId;
+ private String corpId;
+
+ public Pvg(String crmUserId, String roleName, String orgId, String corpId) {
+ this.crmUserId = crmUserId;
+ this.roleName = roleName;
+ this.orgId = orgId;
+ this.corpId = corpId;
+ }
+ }
+
+ public static void set(String crmUserId, String roleName, String orgId, String corpId) {
+ Pvg pvg = new Pvg(crmUserId, roleName, orgId, corpId);
+ pvgContext.set(pvg);
+ }
+
+ public static void remove() {
+ pvgContext.remove();
+ }
+
+ public static boolean exist() {
+ return null != pvgContext.get();
+ }
+
+ public static String getCrmUserId() {
+ Preconditions.checkArgument(null != pvgContext.get() && null != pvgContext.get().crmUserId,
+ "No User in Context");
+ return pvgContext.get().crmUserId;
+ }
+
+ public static String getRoleName() {
+ Preconditions.checkArgument(null != pvgContext.get() && null != pvgContext.get().roleName,
+ "No roleName in Context");
+ return pvgContext.get().roleName;
+ }
+
+ public static String getOrgId() {
+ Preconditions.checkArgument(null != pvgContext.get() && null != pvgContext.get().orgId,
+ "No orgId in Context");
+ return pvgContext.get().orgId;
+ }
+
+ public static String getCorpId() {
+ Preconditions.checkArgument(null != pvgContext.get() && null != pvgContext.get().corpId,
+ "No corpId in Context");
+ return pvgContext.get().corpId;
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/context/TenantContext.java b/halo-core/src/main/java/org/xujin/halo/context/TenantContext.java
new file mode 100644
index 0000000..e74536e
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/context/TenantContext.java
@@ -0,0 +1,49 @@
+package org.xujin.halo.context;
+
+import org.xujin.halo.exception.BizException;
+
+/**
+ * 租户的上下文
+ */
+public class TenantContext {
+
+ private static ThreadLocal tenantContext = new ThreadLocal<>();
+
+ private static class Tenant{
+ String tenantId;
+ String bizCode;
+ private Tenant(String tenantId, String bizCode) {
+ this.tenantId = tenantId;
+ this.bizCode = bizCode;
+ }
+ }
+
+ public static boolean exist(){
+ return null != tenantContext.get();
+ }
+
+ public static String getTenantId() {
+ if (tenantContext.get() == null || tenantContext.get().tenantId == null) {
+ throw new BizException("No tenantId in Context");
+ }
+ return tenantContext.get().tenantId;
+ }
+
+ public static String getBizCode() {
+ if (tenantContext.get() == null || tenantContext.get().bizCode == null) {
+ throw new BizException("No bizCode in Context");
+ }
+ return tenantContext.get().bizCode;
+ }
+
+ public static void set(String tenantId, String bizCode) {
+ Tenant tenant = new Tenant(tenantId, bizCode);
+ tenantContext.set(tenant);
+ }
+
+ public static void remove() {
+ tenantContext.remove();
+ }
+
+}
+
diff --git a/halo-core/src/main/java/org/xujin/halo/convertor/ConvertorI.java b/halo-core/src/main/java/org/xujin/halo/convertor/ConvertorI.java
new file mode 100644
index 0000000..2ecf945
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/convertor/ConvertorI.java
@@ -0,0 +1,25 @@
+package org.xujin.halo.convertor;
+
+import org.xujin.halo.domain.Entity;
+import org.xujin.halo.dto.DTO;
+import org.xujin.halo.repository.DataObject;
+
+/**
+ * Convertor are used to convert Objects among Client Object, Domain Object and Data Object.
+ *
+ * @author xujin
+ */
+public interface ConvertorI {
+
+ default public T entityToClient(Object... entityObject){return null;}
+
+ default public T dataToClient(Object... dataObject){return null;}
+
+ default public T clientToEntity(Object... clientObject){return null;}
+
+ default public T dataToEntity(Object... dataObject){return null;}
+
+ default public T entityToData(Object... entityObject){return null;}
+
+}
+
diff --git a/halo-core/src/main/java/org/xujin/halo/event/EventBus.java b/halo-core/src/main/java/org/xujin/halo/event/EventBus.java
new file mode 100644
index 0000000..e915a5f
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/event/EventBus.java
@@ -0,0 +1,103 @@
+package org.xujin.halo.event;
+
+import org.xujin.halo.dto.Response;
+import org.xujin.halo.dto.event.Event;
+import org.xujin.halo.exception.BasicErrorCode;
+import org.xujin.halo.exception.HaloException;
+import org.xujin.halo.exception.ErrorCodeI;
+import org.xujin.halo.exception.InfraException;
+import org.xujin.halo.logger.Logger;
+import org.xujin.halo.logger.LoggerFactory;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+/**
+ * Event Bus
+ *
+ * @author xujin
+ * @date 2018/3/20
+ */
+@Component
+public class EventBus implements EventBusI{
+ Logger logger = LoggerFactory.getLogger(EventBus.class);
+
+ /**
+ * 默认线程池
+ * 如果处理器无定制线程池,则使用此默认
+ */
+ ExecutorService defaultExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() + 1,
+ Runtime.getRuntime().availableProcessors() + 1,
+ 0L,TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue(1000),new ThreadFactoryBuilder().setNameFormat("event-bus-pool-%d").build());
+
+ @Autowired
+ private EventHub eventHub;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Response fire(Event event) {
+ Response response = null;
+ EventHandlerI eventHandlerI = null;
+ try {
+ eventHandlerI = eventHub.getEventHandler(event.getClass()).get(0);
+ response = eventHandlerI.execute(event);
+ }catch (Exception exception) {
+ response = handleException(eventHandlerI, response, exception);
+ }
+ return response;
+ }
+
+ @Override
+ public void fireAll(Event event){
+ eventHub.getEventHandler(event.getClass()).parallelStream().map(p->{
+ Response response = null;
+ try {
+ response = p.execute(event);
+ }catch (Exception exception) {
+ response = handleException(p, response, exception);
+ }
+ return response;
+ }).collect(Collectors.toList());
+ }
+
+ @Override
+ public void asyncFire(Event event){
+ eventHub.getEventHandler(event.getClass()).parallelStream().map(p->{
+ Response response = null;
+ try {
+ if(null != p.getExecutor()){
+ p.getExecutor().submit(()->p.execute(event));
+ }else{
+ defaultExecutor.submit(()->p.execute(event));
+ }
+ }catch (Exception exception) {
+ response = handleException(p, response, exception);
+ }
+ return response;
+ }).collect(Collectors.toList());
+ }
+
+ private Response handleException(EventHandlerI handler, Response response, Exception exception) {
+ Class responseClz = eventHub.getResponseRepository().get(handler.getClass());
+ try {
+ response = (Response) responseClz.newInstance();
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ throw new InfraException(e.getMessage());
+ }
+ if (exception instanceof HaloException) {
+ ErrorCodeI errCode = ((HaloException) exception).getErrCode();
+ response.setErrCode(errCode.getErrCode());
+ }
+ else {
+ response.setErrCode(BasicErrorCode.SYS_ERROR.getErrCode());
+ }
+ response.setErrMessage(exception.getMessage());
+ logger.error(exception.getMessage(), exception);
+ return response;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/event/EventBusI.java b/halo-core/src/main/java/org/xujin/halo/event/EventBusI.java
new file mode 100644
index 0000000..923956e
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/event/EventBusI.java
@@ -0,0 +1,36 @@
+package org.xujin.halo.event;
+
+import org.xujin.halo.dto.event.Event;
+import org.xujin.halo.dto.Response;
+
+
+/**
+ * EventBus interface
+ * @author xujin
+ * @date 2018/3/20
+ */
+public interface EventBusI {
+
+ /**
+ * Send event to EventBus
+ * 发送事件到事件Bus
+ * @param event
+ * @return Response
+ */
+ public Response fire(Event event);
+
+ /**
+ * fire all handlers which registed the event
+ * 触发已经注册的所有注册事件对应的处理器
+ * @param event
+ * @return Response
+ */
+ public void fireAll(Event event);
+
+ /**
+ * 异步触发对应注册事件的对应的处理器
+ * Async fire all handlers
+ * @param event
+ */
+ public void asyncFire(Event event);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/event/EventHandler.java b/halo-core/src/main/java/org/xujin/halo/event/EventHandler.java
new file mode 100644
index 0000000..85564cd
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/event/EventHandler.java
@@ -0,0 +1,17 @@
+package org.xujin.halo.event;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * 事件处理器
+ * @author xujin
+ * @date 2018/5/20
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Component
+public @interface EventHandler {
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/event/EventHandlerI.java b/halo-core/src/main/java/org/xujin/halo/event/EventHandlerI.java
new file mode 100644
index 0000000..fc39464
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/event/EventHandlerI.java
@@ -0,0 +1,22 @@
+package org.xujin.halo.event;
+
+import org.xujin.halo.dto.event.Event;
+import org.xujin.halo.dto.Response;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * event handler
+ * 事件处理器的抽象接口
+ * @author xujin
+ * @date 2018/6/20
+ */
+public interface EventHandlerI {
+
+ default public ExecutorService getExecutor(){
+ return null;
+ }
+
+ public R execute(E e);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/event/EventHub.java b/halo-core/src/main/java/org/xujin/halo/event/EventHub.java
new file mode 100644
index 0000000..eb99dfa
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/event/EventHub.java
@@ -0,0 +1,54 @@
+package org.xujin.halo.event;
+
+import org.xujin.halo.dto.event.Event;
+import org.xujin.halo.exception.InfraException;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 事件控制中枢
+ * @author xujin
+ * @date 2018/5/20
+ */
+@SuppressWarnings("rawtypes")
+@Component
+public class EventHub {
+ @Getter
+ @Setter
+ private ListMultimap eventRepository = ArrayListMultimap.create();
+
+ @Getter
+ private Map responseRepository = new HashMap<>();
+
+ public List getEventHandler(Class eventClass) {
+ List eventHandlerIList = findHandler(eventClass);
+ if (eventHandlerIList == null || eventHandlerIList.size() == 0) {
+ throw new InfraException(eventClass + "is not registered in eventHub, please register first");
+ }
+ return eventHandlerIList;
+ }
+
+ /**
+ * 注册事件
+ * @param eventClz
+ * @param executor
+ */
+ public void register(Class extends Event> eventClz, EventHandlerI executor){
+ eventRepository.put(eventClz, executor);
+ }
+
+ private List findHandler(Class extends Event> eventClass){
+ List eventHandlerIList = null;
+ Class cls = eventClass;
+ eventHandlerIList = eventRepository.get(cls);
+ return eventHandlerIList;
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/BasicErrorCode.java b/halo-core/src/main/java/org/xujin/halo/exception/BasicErrorCode.java
new file mode 100644
index 0000000..8004a87
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/BasicErrorCode.java
@@ -0,0 +1,42 @@
+package org.xujin.halo.exception;
+
+/**
+ *
+ * 参数错误:1000 - 1999
+ * 业务错误:2000 - 2999
+ * 基础错误:3000 - 3999
+ * 系统错误:4000 - 4999
+ *
+ * 基本的错误定义
+ */
+public enum BasicErrorCode implements ErrorCodeI{
+ PARAM_ERROR("1000" , "请求参数校验错误" , false),
+ BIZ_ERROR("2000" , "业务逻辑错误" , false),
+ INFRA_ERROR("3000" , "基础设施(数据库,缓存,消息等)错误" , true),
+ SYS_ERROR("4000" , "未知的其它系统错误" , true);
+
+ private String errCode;
+ private String errDesc;
+ private boolean retriable;
+
+ private BasicErrorCode(String errCode, String errDesc, boolean retriable){
+ this.errCode = errCode;
+ this.errDesc = errDesc;
+ this.retriable = retriable;
+ }
+
+ @Override
+ public String getErrCode() {
+ return errCode;
+ }
+
+ @Override
+ public String getErrDesc() {
+ return errDesc;
+ }
+
+ @Override
+ public boolean isRetriable() {
+ return retriable;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/BizException.java b/halo-core/src/main/java/org/xujin/halo/exception/BizException.java
new file mode 100644
index 0000000..1179e0c
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/BizException.java
@@ -0,0 +1,21 @@
+package org.xujin.halo.exception;
+
+public class BizException extends HaloException {
+
+ private static final long serialVersionUID = 1L;
+
+ public BizException(String errMessage){
+ super(errMessage);
+ this.setErrCode(BasicErrorCode.BIZ_ERROR);
+ }
+
+ public BizException(ErrorCodeI errCode, String errMessage){
+ super(errMessage);
+ this.setErrCode(errCode);
+ }
+
+ public BizException(String errMessage, Throwable e) {
+ super(errMessage, e);
+ this.setErrCode(BasicErrorCode.BIZ_ERROR);
+ }
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/ErrorCodeI.java b/halo-core/src/main/java/org/xujin/halo/exception/ErrorCodeI.java
new file mode 100644
index 0000000..a3550a8
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/ErrorCodeI.java
@@ -0,0 +1,15 @@
+package org.xujin.halo.exception;
+
+/**
+ * Extends your error codes in your App by implements this Interface.
+ *
+ * Created by xujin on 2018/4/18.
+ */
+public interface ErrorCodeI {
+
+ public String getErrCode();
+
+ public String getErrDesc();
+
+ public boolean isRetriable();
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/HaloException.java b/halo-core/src/main/java/org/xujin/halo/exception/HaloException.java
new file mode 100644
index 0000000..51ca528
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/HaloException.java
@@ -0,0 +1,31 @@
+package org.xujin.halo.exception;
+
+/**
+ *
+ * Halo框架抽象异常
+ *
+ * @author
+ */
+public abstract class HaloException extends RuntimeException{
+
+ private static final long serialVersionUID = 1L;
+
+ private ErrorCodeI errCode;
+
+ public HaloException(String errMessage){
+ super(errMessage);
+ }
+
+ public HaloException(String errMessage, Throwable e) {
+ super(errMessage, e);
+ }
+
+ public ErrorCodeI getErrCode() {
+ return errCode;
+ }
+
+ public void setErrCode(ErrorCodeI errCode) {
+ this.errCode = errCode;
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/InfraException.java b/halo-core/src/main/java/org/xujin/halo/exception/InfraException.java
new file mode 100644
index 0000000..2b73a43
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/InfraException.java
@@ -0,0 +1,22 @@
+package org.xujin.halo.exception;
+
+/**
+ *
+ * Infrastructure Exception
+ * 基础设施层异常
+ * @author xujin
+ */
+public class InfraException extends HaloException {
+
+ private static final long serialVersionUID = 1L;
+
+ public InfraException(String errMessage){
+ super(errMessage);
+ this.setErrCode(BasicErrorCode.INFRA_ERROR);
+ }
+
+ public InfraException(String errMessage, Throwable e) {
+ super(errMessage, e);
+ this.setErrCode(BasicErrorCode.INFRA_ERROR);
+ }
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/ParamException.java b/halo-core/src/main/java/org/xujin/halo/exception/ParamException.java
new file mode 100644
index 0000000..ec87819
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/ParamException.java
@@ -0,0 +1,24 @@
+package org.xujin.halo.exception;
+
+/**
+ * 参数异常
+ */
+public class ParamException extends HaloException {
+
+ private static final long serialVersionUID = 1L;
+
+ public ParamException(String errMessage){
+ super(errMessage);
+ this.setErrCode(BasicErrorCode.PARAM_ERROR);
+ }
+
+ public ParamException(ErrorCodeI errCode, String errMessage){
+ super(errMessage);
+ this.setErrCode(errCode);
+ }
+
+ public ParamException(String errMessage, Throwable e) {
+ super(errMessage, e);
+ this.setErrCode(BasicErrorCode.PARAM_ERROR);
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/Preconditions.java b/halo-core/src/main/java/org/xujin/halo/exception/Preconditions.java
new file mode 100644
index 0000000..1e8e9f9
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/Preconditions.java
@@ -0,0 +1,32 @@
+package org.xujin.halo.exception;
+
+/**
+ * Preconditions 先决条件
+ * @author xujin
+ */
+public class Preconditions {
+
+ public static void checkArgument(boolean expression) {
+ if (!expression) {
+ throw new ParamException("");
+ }
+ }
+
+ public static void checkArgument(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new ParamException(String.valueOf(errorMessage));
+ }
+ }
+
+ public static void checkState(boolean expression) {
+ if (!expression) {
+ throw new BizException("");
+ }
+ }
+
+ public static void checkState(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new BizException(String.valueOf(errorMessage));
+ }
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/exception/SystemException.java b/halo-core/src/main/java/org/xujin/halo/exception/SystemException.java
new file mode 100644
index 0000000..832d6e6
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/exception/SystemException.java
@@ -0,0 +1,25 @@
+package org.xujin.halo.exception;
+
+/**
+ * 系统异常
+ *
+ * @author xujin
+ */
+public class SystemException extends HaloException {
+
+ private static final long serialVersionUID = 4355163994767354840L;
+
+ public SystemException(String errMessage) {
+ super(errMessage);
+ }
+
+ public SystemException(ErrorCodeI errCode, String errMessage) {
+ super(errMessage);
+ this.setErrCode(errCode);
+ }
+
+ public SystemException(String errMessage, ErrorCodeI errorCode, Throwable e) {
+ super(errMessage, e);
+ this.setErrCode(errorCode);
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/extension/Extension.java b/halo-core/src/main/java/org/xujin/halo/extension/Extension.java
new file mode 100644
index 0000000..12d4cf9
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/extension/Extension.java
@@ -0,0 +1,30 @@
+package org.xujin.halo.extension;
+
+import org.springframework.core.annotation.AliasFor;
+import org.xujin.halo.common.CoreConstant;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * Extension
+ * @author xujin
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Component
+public @interface Extension {
+
+ /**
+ * 一级业务code
+ * @return
+ */
+ String bizCode() default CoreConstant.DEFAULT_BIZ_CODE;
+
+ /**
+ * 二级业务code
+ * @return
+ */
+ String tenantId() default CoreConstant.DEFAULT_TENANT_ID;
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/extension/ExtensionCoordinate.java b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionCoordinate.java
new file mode 100644
index 0000000..1e1b61c
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionCoordinate.java
@@ -0,0 +1,61 @@
+package org.xujin.halo.extension;
+
+import lombok.Data;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * Extension Coordinate(扩展点坐标) used to uniquely position a Extension
+ * @author xujin
+ */
+@Data
+public class ExtensionCoordinate {
+
+ private String extensionPoint;
+ private String bizCode;
+ private String tenantId;
+
+ /**
+ * @param extensionPoint
+ * @param bizCode
+ * @param tenantId
+ */
+ public ExtensionCoordinate(String extensionPoint, String bizCode, String tenantId){
+ super();
+ this.extensionPoint = extensionPoint;
+ this.bizCode = bizCode;
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((bizCode == null) ? 0 : bizCode.hashCode());
+ result = prime * result + ((extensionPoint == null) ? 0 : extensionPoint.hashCode());
+ result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
+ return result;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ ExtensionCoordinate other = (ExtensionCoordinate) obj;
+ if (bizCode == null) {
+ if (other.bizCode != null) return false;
+ } else if (!bizCode.equals(other.bizCode)) return false;
+ if (extensionPoint == null) {
+ if (other.extensionPoint != null) return false;
+ } else if (!extensionPoint.equals(other.extensionPoint)) return false;
+ if (tenantId == null) {
+ if (other.tenantId != null) return false;
+ } else if (!tenantId.equals(other.tenantId)) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/extension/ExtensionExecutor.java b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionExecutor.java
new file mode 100644
index 0000000..9e4f1b8
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionExecutor.java
@@ -0,0 +1,65 @@
+package org.xujin.halo.extension;
+
+import org.xujin.halo.boot.ComponentExecutor;
+import org.xujin.halo.common.CoreConstant;
+import org.xujin.halo.common.DefaultBizCode;
+import org.xujin.halo.context.TenantContext;
+import org.xujin.halo.exception.InfraException;
+import org.xujin.halo.logger.Logger;
+import org.xujin.halo.logger.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 扩展点执行器-ExtensionExecutor
+ * @author xujin
+ */
+@Component
+public class ExtensionExecutor extends ComponentExecutor{
+
+ private Logger logger = LoggerFactory.getLogger(ExtensionExecutor.class);
+
+ @Autowired
+ private ExtensionRepository extensionRepository;
+
+ @Override
+ protected C locateComponent(Class targetClz) {
+ C extension = locateExtension(targetClz);
+ logger.debug("[Located Extension]: "+extension.getClass().getSimpleName());
+ return locateExtension(targetClz);
+ }
+
+ /**
+ * @param targetClz
+ */
+ @SuppressWarnings("unchecked")
+ protected Ext locateExtension(Class targetClz) {
+ String bizCode = TenantContext.getBizCode();
+ String tenantId = TenantContext.getTenantId();
+ ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(targetClz.getSimpleName(), bizCode, tenantId);
+ /**
+ * 1.First search key is: extensionPoint + bizCode + tenantId
+ */
+ Ext extension = (Ext)extensionRepository.getExtensionRepo().get(extensionCoordinate);
+ if (extension != null) {
+ return extension;
+ }
+ /**
+ * 2.Second search key is: extensionPoint + bizCode
+ */
+ extensionCoordinate.setTenantId(CoreConstant.DEFAULT_TENANT_ID);
+ extension = (Ext)extensionRepository.getExtensionRepo().get(extensionCoordinate);
+ if (extension != null) {
+ return extension;
+ }
+ /**
+ * 3.Third search key is: extensionPoint
+ */
+ extensionCoordinate.setBizCode(DefaultBizCode.DEFAULT_BIZ_CODE);
+ extension = (Ext)extensionRepository.getExtensionRepo().get(extensionCoordinate);
+ if (extension != null) {
+ return extension;
+ }
+ throw new InfraException("Can not find extension for ExtensionPoint: "+targetClz);
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/extension/ExtensionPointI.java b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionPointI.java
new file mode 100644
index 0000000..3f4bf6a
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionPointI.java
@@ -0,0 +1,10 @@
+package org.xujin.halo.extension;
+
+/**
+ * ExtensionPointI is the parent interface of all ExtensionPoints
+ * 扩展点表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明,然后用Extension(扩展)去实现扩展点。
+ * @author xujin
+ */
+public interface ExtensionPointI {
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/extension/ExtensionRepository.java b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionRepository.java
new file mode 100644
index 0000000..216fe27
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/extension/ExtensionRepository.java
@@ -0,0 +1,20 @@
+package org.xujin.halo.extension;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import lombok.Getter;
+
+/**
+ * ExtensionRepository
+ * @author xujin
+ */
+@Component
+public class ExtensionRepository {
+
+ @Getter
+ private Map extensionRepo = new HashMap<>();
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/extensionpoint/MqEventConsumerExtPt.java b/halo-core/src/main/java/org/xujin/halo/extensionpoint/MqEventConsumerExtPt.java
new file mode 100644
index 0000000..7d4b8cd
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/extensionpoint/MqEventConsumerExtPt.java
@@ -0,0 +1,13 @@
+package org.xujin.halo.extensionpoint;
+
+import org.xujin.halo.dto.Response;
+
+/**
+ * 消息消费扩展点
+ * @author xujin
+ *
+ */
+public interface MqEventConsumerExtPt {
+
+ Response receive(String msg);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/logger/Logger.java b/halo-core/src/main/java/org/xujin/halo/logger/Logger.java
new file mode 100644
index 0000000..448e493
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/logger/Logger.java
@@ -0,0 +1,84 @@
+package org.xujin.halo.logger;
+
+/**
+ * This is a typical DIP, by depending on ourselves logger, we can change the underline logger vendor easily
+ *
+ * @author xujin 2017年10月23日 下午11:58:54
+ */
+public interface Logger {
+
+ /**
+ * Log a message at the DEBUG level.
+ *
+ * @param msg the message string to be logged
+ */
+ public void debug(String msg);
+
+ /**
+ * Log a message at the DEBUG level. support format.
+ *
+ * @param msg
+ * @param args
+ */
+ default public void debug(String msg, Object... args){
+ debug(String.format(msg, args));
+ }
+
+ /**
+ * Log a message at the INFO level.
+ *
+ * @param msg the message string to be logged
+ */
+ public void info(String msg);
+
+ /**
+ * Log a message at the INFO level. support format.
+ *
+ * @param msg
+ * @param args
+ */
+ default public void info(String msg, Object... args){
+ info(String.format(msg, args));
+ }
+
+ /**
+ * Log a message at the WARN level.
+ *
+ * @param msg the message string to be logged
+ */
+ public void warn(String msg);
+
+ /**
+ * Log a message at the WARN level. support format.
+ *
+ * @param msg
+ * @param args
+ */
+ default public void warn(String msg, Object... args){
+ warn(String.format(msg, args));
+ }
+ /**
+ * Log a message at the ERROR level.
+ *
+ * @param msg the message string to be logged
+ */
+ public void error(String msg);
+
+ /**
+ * Log a message at the ERROR level. support format.
+ * @param msg
+ * @param args
+ */
+ default public void error(String msg, Object... args){
+ error(String.format(msg, args));
+ }
+
+ /**
+ * Log an exception (throwable) at the ERROR level with an
+ * accompanying message.
+ *
+ * @param msg the message accompanying the exception
+ * @param t the exception (throwable) to log
+ */
+ public void error(String msg, Throwable t);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/logger/LoggerFactory.java b/halo-core/src/main/java/org/xujin/halo/logger/LoggerFactory.java
new file mode 100644
index 0000000..e769c85
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/logger/LoggerFactory.java
@@ -0,0 +1,33 @@
+package org.xujin.halo.logger;
+
+public class LoggerFactory {
+
+ private static boolean useSysLogger = true;
+
+ public static Logger getLogger(Class> clazz) {
+ if(useSysLogger) {
+ return SysLogger.singleton;
+ }
+ org.slf4j.Logger slfjLogger = org.slf4j.LoggerFactory.getLogger(clazz);
+ return new SLFJLogger(slfjLogger);
+ }
+
+ public static Logger getLogger(String loggerName) {
+ if(useSysLogger) {
+ return SysLogger.singleton;
+ }
+ org.slf4j.Logger slfjLogger = org.slf4j.LoggerFactory.getLogger(loggerName);
+ return new SLFJLogger(slfjLogger);
+ }
+
+ /**
+ * This is just for test purpose, don't use it on product!
+ */
+ public static void activateSysLogger() {
+ useSysLogger = true;
+ }
+
+ public static void deactivateSysLogger() {
+ useSysLogger = false;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/logger/SLFJLogger.java b/halo-core/src/main/java/org/xujin/halo/logger/SLFJLogger.java
new file mode 100644
index 0000000..c24eb87
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/logger/SLFJLogger.java
@@ -0,0 +1,39 @@
+package org.xujin.halo.logger;
+
+public class SLFJLogger implements Logger {
+
+ private org.slf4j.Logger slfjLogger;
+
+ public SLFJLogger(org.slf4j.Logger slfjLogger) {
+ this.slfjLogger = slfjLogger;
+ }
+
+ @Override
+ public void debug(String msg) {
+ slfjLogger.debug(msg);
+ }
+
+ @Override
+ public void info(String msg) {
+ slfjLogger.info(msg);
+
+ }
+
+ @Override
+ public void warn(String msg) {
+ slfjLogger.warn(msg);
+
+ }
+
+ @Override
+ public void error(String msg) {
+ slfjLogger.error(msg);
+
+ }
+
+ @Override
+ public void error(String msg, Throwable t) {
+ slfjLogger.error(msg, t);
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/logger/SysLogger.java b/halo-core/src/main/java/org/xujin/halo/logger/SysLogger.java
new file mode 100644
index 0000000..8302b9d
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/logger/SysLogger.java
@@ -0,0 +1,33 @@
+package org.xujin.halo.logger;
+
+public class SysLogger implements Logger{
+
+ public static SysLogger singleton = new SysLogger();
+
+ @Override
+ public void debug(String msg) {
+ System.out.println("DEBUG: "+msg);
+ }
+
+ @Override
+ public void info(String msg) {
+ System.out.println("INFO: "+msg);
+ }
+
+ @Override
+ public void warn(String msg) {
+ System.out.println("WARN: "+msg);
+ }
+
+ @Override
+ public void error(String msg) {
+ System.err.println("ERROR: "+msg);
+ }
+
+ @Override
+ public void error(String msg, Throwable t) {
+ System.err.println("ERROR: "+msg);
+ t.printStackTrace();
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/pattern/filter/Filter.java b/halo-core/src/main/java/org/xujin/halo/pattern/filter/Filter.java
new file mode 100644
index 0000000..daa2081
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/pattern/filter/Filter.java
@@ -0,0 +1,10 @@
+package org.xujin.halo.pattern.filter;
+
+/**
+ *
+ */
+public interface Filter {
+
+ void doFilter(Object context, FilterInvoker nextFilter);
+
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterChain.java b/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterChain.java
new file mode 100644
index 0000000..56a8ceb
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterChain.java
@@ -0,0 +1,19 @@
+package org.xujin.halo.pattern.filter;
+
+/**
+ * 拦截器链
+ * @author xujin
+ * @date 2018/04/17
+ */
+public class FilterChain{
+
+ private FilterInvoker header;
+
+ public void doFilter(Object context){
+ header.invoke(context);
+ }
+
+ public void setHeader(FilterInvoker header) {
+ this.header = header;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterChainFactory.java b/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterChainFactory.java
new file mode 100644
index 0000000..7946d50
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterChainFactory.java
@@ -0,0 +1,30 @@
+package org.xujin.halo.pattern.filter;
+
+import org.xujin.halo.common.ApplicationContextHelper;
+
+/**
+ * 责任链模式工厂
+ * @author xujin
+ * @date 2018/04/17
+ */
+public class FilterChainFactory {
+
+ public static FilterChain buildFilterChain(Class... filterClsList) {
+ FilterInvoker last = new FilterInvoker(){};
+ FilterChain filterChain = new FilterChain();
+ for(int i = filterClsList.length - 1; i >= 0; i--){
+ FilterInvoker next = last;
+ Filter filter = (Filter)ApplicationContextHelper.getBean(filterClsList[i]);
+ last = new FilterInvoker(){
+ @Override
+ public void invoke(Object context) {
+ filter.doFilter(context, next);
+ }
+ };
+ }
+ filterChain.setHeader(last);
+ return filterChain;
+ }
+
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterInvoker.java b/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterInvoker.java
new file mode 100644
index 0000000..c95d0a8
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/pattern/filter/FilterInvoker.java
@@ -0,0 +1,10 @@
+package org.xujin.halo.pattern.filter;
+
+/**
+ * @author xujin
+ * @date 2018/04/17
+ */
+public interface FilterInvoker {
+
+ default public void invoke(Object context){};
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/repository/DataObject.java b/halo-core/src/main/java/org/xujin/halo/repository/DataObject.java
new file mode 100644
index 0000000..c9a923c
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/repository/DataObject.java
@@ -0,0 +1,11 @@
+package org.xujin.halo.repository;
+
+import java.io.Serializable;
+
+/**
+ * This is the parent of all Data Objects.
+ * Data object only has fields and according getters and setters, you can also call it anemic objects
+ * @author xujin 2017年10月27日 上午10:21:01
+ */
+public interface DataObject extends Serializable {
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/repository/DataTunnel.java b/halo-core/src/main/java/org/xujin/halo/repository/DataTunnel.java
new file mode 100644
index 0000000..06263a5
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/repository/DataTunnel.java
@@ -0,0 +1,23 @@
+package org.xujin.halo.repository;
+
+import java.util.List;
+
+/**
+ * Data Tunnel is the real Data CRUD Operator may interact with DB, service, cache etc...
+ *
+ * @author xujin 2017年10月27日 上午10:34:17
+ */
+public interface DataTunnel {
+
+ public T create(T dataObject);
+
+ default public void delete(T dataObject) {};
+
+ default public void update(T dataObject){};
+
+ default public List list(T entity){return null;};
+
+ public T get(String id);
+
+ public List getByEntity(T entity);
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/repository/RepositoryI.java b/halo-core/src/main/java/org/xujin/halo/repository/RepositoryI.java
new file mode 100644
index 0000000..692eb80
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/repository/RepositoryI.java
@@ -0,0 +1,9 @@
+package org.xujin.halo.repository;
+
+/**
+ * This is Repository pattern which decouples the data access from concrete data tunnels.
+ *
+ * @author xujin 2017年10月27日 上午11:07:26
+ */
+public interface RepositoryI {
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/PlainRuleRepository.java b/halo-core/src/main/java/org/xujin/halo/rule/PlainRuleRepository.java
new file mode 100644
index 0000000..cc66685
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/PlainRuleRepository.java
@@ -0,0 +1,18 @@
+package org.xujin.halo.rule;
+
+import lombok.Getter;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Plain Rule is Rule Component without ExtensionPoint
+ * @author xujin
+ * @date 2018/5/21
+ */
+@Component
+public class PlainRuleRepository {
+ @Getter
+ private Map, RuleI> plainRules = new HashMap<>();
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/RuleExecutor.java b/halo-core/src/main/java/org/xujin/halo/rule/RuleExecutor.java
new file mode 100644
index 0000000..2c860a0
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/RuleExecutor.java
@@ -0,0 +1,30 @@
+package org.xujin.halo.rule;
+
+import org.xujin.halo.extension.ExtensionExecutor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * RuleExecutor
+ *
+ * Note that Rule is extensible as long as @Extension is added
+ *
+ * @author xujin 2017-11-04
+ */
+@Component
+public class RuleExecutor extends ExtensionExecutor {
+
+ @Autowired
+ private PlainRuleRepository plainRuleRepository;
+
+ @Override
+ protected C locateComponent(Class targetClz) {
+ C rule = (C) plainRuleRepository.getPlainRules().get(targetClz);
+ return null != rule ? rule : super.locateComponent(targetClz);
+ }
+
+ public void validate(Class extends RuleI> targetClz, Object... candidate) {
+ RuleI rule = this.locateComponent(targetClz);
+ rule.validate(candidate);
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/RuleI.java b/halo-core/src/main/java/org/xujin/halo/rule/RuleI.java
new file mode 100644
index 0000000..54aea7f
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/RuleI.java
@@ -0,0 +1,24 @@
+package org.xujin.halo.rule;
+
+/**
+ * 业务规则接口,适合领域层的业务规则检查
+ * @author xueliang.sxl
+ *
+ */
+public interface RuleI {
+
+ /**
+ * 默认的规则检查接口,子接口可自定义
+ * @param roleContext
+ * @return
+ */
+ default boolean check(Object roleContext){
+ return true;
+ }
+
+ /**
+ * This is specific for Validation cases where no Return is needed
+ * @param candidate
+ */
+ default void validate(Object... candidate){}
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/AbstractRule.java b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/AbstractRule.java
new file mode 100644
index 0000000..724f515
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/AbstractRule.java
@@ -0,0 +1,24 @@
+package org.xujin.halo.rule.ruleengine;
+
+import org.xujin.halo.rule.RuleI;
+
+/**
+ * 业务规则的抽象实现,可用于组合规则
+ * @author xueliang.sxl
+ *
+ */
+public abstract class AbstractRule implements RuleI {
+
+ public RuleI and(RuleI other) {
+ return new AndRule(this, other);
+ }
+
+ public RuleI or(RuleI other) {
+ return new OrRule(this, other);
+ }
+
+ public RuleI not() {
+ return new NotRule(this);
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/AndRule.java b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/AndRule.java
new file mode 100644
index 0000000..353d49b
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/AndRule.java
@@ -0,0 +1,24 @@
+package org.xujin.halo.rule.ruleengine;
+
+import org.xujin.halo.rule.RuleI;
+
+/**
+ * 组合And的业务规则
+ * @author xueliang.sxl
+ *
+ */
+public class AndRule extends AbstractRule {
+ RuleI one;
+ RuleI other;
+
+ public AndRule(RuleI x, RuleI y){
+ one = x;
+ other = y;
+ }
+
+ @Override
+ public boolean check(Object candidate) {
+ return one.check(candidate) && other.check(candidate);
+ }
+
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/NotRule.java b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/NotRule.java
new file mode 100644
index 0000000..af23899
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/NotRule.java
@@ -0,0 +1,22 @@
+package org.xujin.halo.rule.ruleengine;
+
+import org.xujin.halo.rule.RuleI;
+
+/**
+ * 组合Not的业务规则
+ * @author xueliang.sxl
+ *
+ */
+public class NotRule extends AbstractRule {
+ RuleI wrapped;
+
+ public NotRule(RuleI x){
+ wrapped = x;
+ }
+
+ @Override
+ public boolean check(Object candidate) {
+ return !wrapped.check(candidate);
+ }
+
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/OrRule.java b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/OrRule.java
new file mode 100644
index 0000000..a654524
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/rule/ruleengine/OrRule.java
@@ -0,0 +1,24 @@
+package org.xujin.halo.rule.ruleengine;
+
+import org.xujin.halo.rule.RuleI;
+
+/**
+ * 组合Or的业务规则
+ * @author xueliang.sxl
+ *
+ */
+public class OrRule extends AbstractRule {
+ RuleI one;
+ RuleI other;
+
+ public OrRule(RuleI x, RuleI y){
+ one = x;
+ other = y;
+ }
+
+ @Override
+ public boolean check(Object candidate) {
+ return one.check(candidate) || other.check(candidate);
+ }
+
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/validator/HaloMessageInterpolator.java b/halo-core/src/main/java/org/xujin/halo/validator/HaloMessageInterpolator.java
new file mode 100644
index 0000000..09b6f26
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/validator/HaloMessageInterpolator.java
@@ -0,0 +1,19 @@
+package org.xujin.halo.validator;
+
+import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
+
+import java.util.Locale;
+
+/**
+ * @author xujin
+ * @date 2018/6/25
+ */
+public class HaloMessageInterpolator extends ResourceBundleMessageInterpolator{
+
+ @Override
+ public String interpolate(String message, Context context) {
+ //Use English Locale
+ String resolvedMessage = super.interpolate(message, context, Locale.ENGLISH);
+ return resolvedMessage;
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/validator/PlainValidatorRepository.java b/halo-core/src/main/java/org/xujin/halo/validator/PlainValidatorRepository.java
new file mode 100644
index 0000000..5837ccd
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/validator/PlainValidatorRepository.java
@@ -0,0 +1,18 @@
+package org.xujin.halo.validator;
+
+import lombok.Getter;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Plain Validator is Validator Component without ExtensionPoint
+ * @author xujin
+ * @date 2018/6/21
+ */
+@Component
+public class PlainValidatorRepository {
+ @Getter
+ private Map, ValidatorI> plainValidators = new HashMap<>();
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/validator/ValidatorCompoiste.java b/halo-core/src/main/java/org/xujin/halo/validator/ValidatorCompoiste.java
new file mode 100644
index 0000000..8da88f1
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/validator/ValidatorCompoiste.java
@@ -0,0 +1,50 @@
+package org.xujin.halo.validator;
+
+import com.google.common.collect.Lists;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.List;
+
+/**
+ * 使用组合模式,允许一个Validator可以组合多个其它的Validator完成validation的任务
+ *
+ * @author xujin 2017-11-04
+ */
+
+public abstract class ValidatorCompoiste implements ValidatorI, InitializingBean {
+
+ private List validators;
+
+ /**
+ * Composite other validators if necessary
+ */
+ abstract protected void addOtherValidators();
+
+ /**
+ * Aside from composited validators, do its own validation here
+ * @param candidate
+ */
+ abstract protected void doValidate(Object candidate);
+
+ protected void add(ValidatorI validator) {
+ if (validators == null) {
+ validators = Lists.newArrayList();
+ }
+ validators.add(validator);
+ }
+
+ @Override
+ public void validate(Object candidate) {
+ if (validators != null) {
+ for (ValidatorI validator : validators) {
+ validator.validate(candidate);
+ }
+ }
+ doValidate(candidate);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ addOtherValidators();
+ }
+}
diff --git a/halo-core/src/main/java/org/xujin/halo/validator/ValidatorExecutor.java b/halo-core/src/main/java/org/xujin/halo/validator/ValidatorExecutor.java
new file mode 100644
index 0000000..45bdbac
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/validator/ValidatorExecutor.java
@@ -0,0 +1,30 @@
+
+package org.xujin.halo.validator;
+
+import org.xujin.halo.extension.ExtensionExecutor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * ValidatorExecutor
+ *
+ * @author xujin 2017-11-04
+ */
+@Component
+public class ValidatorExecutor extends ExtensionExecutor {
+
+ @Autowired
+ private PlainValidatorRepository plainValidatorRepository;
+
+ public void validate(Class extends ValidatorI> targetClz, Object candidate) {
+ ValidatorI validator = locateComponent(targetClz);
+ validator.validate(candidate);
+ }
+
+ @Override
+ protected C locateComponent(Class targetClz) {
+ C validator = (C) plainValidatorRepository.getPlainValidators().get(targetClz);
+ return null != validator ? validator : super.locateComponent(targetClz);
+ }
+
+}
\ No newline at end of file
diff --git a/halo-core/src/main/java/org/xujin/halo/validator/ValidatorI.java b/halo-core/src/main/java/org/xujin/halo/validator/ValidatorI.java
new file mode 100644
index 0000000..d34bf80
--- /dev/null
+++ b/halo-core/src/main/java/org/xujin/halo/validator/ValidatorI.java
@@ -0,0 +1,16 @@
+
+package org.xujin.halo.validator;
+
+/**
+ * Validator Interface
+ * @author xujin 2017-11-04
+ */
+public interface ValidatorI {
+
+ /**
+ * Validate candidate, throw according exception if failed
+ * @param candidate
+ */
+ public void validate(Object candidate);
+
+}
diff --git a/halo-core/src/main/resources/sample.properties b/halo-core/src/main/resources/sample.properties
new file mode 100644
index 0000000..e69de29
diff --git a/halo-core/src/test/java/org/xujin/halo/TestConfig.java b/halo-core/src/test/java/org/xujin/halo/TestConfig.java
new file mode 100644
index 0000000..2c069d6
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/TestConfig.java
@@ -0,0 +1,27 @@
+package org.xujin.halo;
+
+import org.xujin.halo.boot.Bootstrap;
+import org.springframework.context.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TestConfig
+ *
+ * @author xujin 2018-01-06 7:57 AM
+ */
+@Configuration
+@ComponentScan("org.xujin.halo")
+@PropertySource(value = {"/sample.properties"})
+public class TestConfig {
+
+ @Bean(initMethod = "init")
+ public Bootstrap bootstrap() {
+ Bootstrap bootstrap = new Bootstrap();
+ List packagesToScan = new ArrayList<>();
+ packagesToScan.add("org.xujin.halo.test");
+ bootstrap.setPackages(packagesToScan);
+ return bootstrap;
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/CustomerCommandTest.java b/halo-core/src/test/java/org/xujin/halo/test/CustomerCommandTest.java
new file mode 100644
index 0000000..0b89b17
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/CustomerCommandTest.java
@@ -0,0 +1,122 @@
+package org.xujin.halo.test;
+
+import org.xujin.halo.TestConfig;
+import org.xujin.halo.context.TenantContext;
+import org.xujin.halo.dto.Response;
+import org.xujin.halo.exception.BasicErrorCode;
+import org.xujin.halo.test.customer.*;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.xujin.halo.test.customer.*;
+
+/**
+ * CustomerCommandTest
+ *
+ * @author xujin 2018-01-06 7:55 PM
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {TestConfig.class})
+public class CustomerCommandTest {
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Autowired
+ private CustomerServiceI customerService;
+
+ @Value("${bizCode}")
+ private String bizCode;
+
+ @Before
+ public void setUp() {
+ TenantContext.set(Constants.TENANT_ID, bizCode);
+ }
+
+ @Test
+ public void testBizOneAddCustomerSuccess(){
+ //1. Prepare
+ TenantContext.set(Constants.TENANT_ID, Constants.BIZ_1);
+ AddCustomerCmd addCustomerCmd = new AddCustomerCmd();
+ CustomerCO customerCO = new CustomerCO();
+ customerCO.setCompanyName("xxxx");
+ customerCO.setSource(Constants.SOURCE_RFQ);
+ customerCO.setCustomerType(CustomerType.IMPORTANT);
+ addCustomerCmd.setCustomerCO(customerCO);
+
+ //2. Execute
+ Response response = customerService.addCustomer(addCustomerCmd);
+
+ //3. Expect Success
+ Assert.assertTrue(response.isSuccess());
+ }
+
+ @Test
+ public void testBizOneAddCustomerFailure(){
+ //1. Prepare
+ TenantContext.set(Constants.TENANT_ID, Constants.BIZ_1);
+ AddCustomerCmd addCustomerCmd = new AddCustomerCmd();
+ CustomerCO customerCO = new CustomerCO();
+ customerCO.setCompanyName("xxxx");
+ customerCO.setSource(Constants.SOURCE_AD);
+ customerCO.setCustomerType(CustomerType.IMPORTANT);
+ addCustomerCmd.setCustomerCO(customerCO);
+
+ //2. Execute
+ Response response = customerService.addCustomer(addCustomerCmd);
+
+ //3. Expect exception
+ Assert.assertFalse(response.isSuccess());
+ Assert.assertEquals(response.getErrCode(), BasicErrorCode.BIZ_ERROR.getErrCode());
+ }
+
+ @Test
+ public void testBizTwoAddCustomer(){
+ //1. Prepare
+ TenantContext.set(Constants.TENANT_ID, Constants.BIZ_2);
+ AddCustomerCmd addCustomerCmd = new AddCustomerCmd();
+ CustomerCO customerCO = new CustomerCO();
+ customerCO.setCompanyName("xxxx");
+ customerCO.setSource(Constants.SOURCE_AD);
+ customerCO.setCustomerType(CustomerType.IMPORTANT);
+ addCustomerCmd.setCustomerCO(customerCO);
+
+ //2. Execute
+ Response response = customerService.addCustomer(addCustomerCmd);
+
+ //3. Expect Success
+ Assert.assertTrue(response.isSuccess());
+ }
+
+ @Test
+ public void testCompanyTypeViolation(){
+ AddCustomerCmd addCustomerCmd = new AddCustomerCmd();
+ CustomerCO customerCO = new CustomerCO();
+ customerCO.setCompanyName("xxxx");
+ customerCO.setSource("p4p");
+ customerCO.setCustomerType(CustomerType.VIP);
+ addCustomerCmd.setCustomerCO(customerCO);
+ Response response = customerService.addCustomer(addCustomerCmd);
+
+ //Expect biz exception
+ Assert.assertFalse(response.isSuccess());
+ Assert.assertEquals(response.getErrCode(), BasicErrorCode.BIZ_ERROR.getErrCode());
+ }
+
+ @Test
+ public void testParamValidationFail(){
+ AddCustomerCmd addCustomerCmd = new AddCustomerCmd();
+ Response response = customerService.addCustomer(addCustomerCmd);
+
+ //Expect parameter validation error
+ Assert.assertFalse(response.isSuccess());
+ Assert.assertEquals(response.getErrCode(), BasicErrorCode.PARAM_ERROR.getErrCode());
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/EntityPrototypeTest.java b/halo-core/src/test/java/org/xujin/halo/test/EntityPrototypeTest.java
new file mode 100644
index 0000000..6969658
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/EntityPrototypeTest.java
@@ -0,0 +1,33 @@
+package org.xujin.halo.test;
+
+import org.xujin.halo.TestConfig;
+import org.xujin.halo.common.ApplicationContextHelper;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * EntityPrototypeTest
+ *
+ * @author xujin
+ * @date 2018-01-07 12:34 PM
+ */
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {TestConfig.class})
+public class EntityPrototypeTest {
+
+ @Test
+ public void testPrototype(){
+ CustomerEntity customerEntity1 = (CustomerEntity)ApplicationContextHelper.getBean(CustomerEntity.class);
+ System.out.println(customerEntity1);
+ CustomerEntity customerEntity2 = (CustomerEntity)ApplicationContextHelper.getBean(CustomerEntity.class);
+ System.out.println(customerEntity2);
+
+ Assert.assertEquals(customerEntity1, customerEntity2);
+ Assert.assertFalse(customerEntity1 == customerEntity2); //It should be different objects
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/SpringConfigTest.java b/halo-core/src/test/java/org/xujin/halo/test/SpringConfigTest.java
new file mode 100644
index 0000000..dc73ee7
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/SpringConfigTest.java
@@ -0,0 +1,17 @@
+package org.xujin.halo.test;
+
+import org.xujin.halo.TestConfig;
+import org.xujin.halo.boot.Bootstrap;
+import org.xujin.halo.boot.RegisterFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class SpringConfigTest {
+
+ public static void main(String[] args) {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
+ Bootstrap bootstrap = (Bootstrap)context.getBean("bootstrap");
+ RegisterFactory registerFactory = (RegisterFactory)context.getBean("registerFactory");
+ System.out.println(registerFactory);
+ System.out.println(bootstrap.getPackages());
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/AddCustomerCmd.java b/halo-core/src/test/java/org/xujin/halo/test/customer/AddCustomerCmd.java
new file mode 100644
index 0000000..7245722
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/AddCustomerCmd.java
@@ -0,0 +1,21 @@
+package org.xujin.halo.test.customer;
+
+
+import org.xujin.halo.dto.Command;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+/**
+ * AddCustomerCmd
+ *
+ * @author xujin 2018-01-06 7:28 PM
+ */
+@Data
+public class AddCustomerCmd extends Command {
+
+ @NotNull
+ @Valid
+ private CustomerCO customerCO;
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/AddCustomerCmdExe.java b/halo-core/src/test/java/org/xujin/halo/test/customer/AddCustomerCmdExe.java
new file mode 100644
index 0000000..b0fc03e
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/AddCustomerCmdExe.java
@@ -0,0 +1,47 @@
+package org.xujin.halo.test.customer;
+
+import org.xujin.halo.command.Command;
+import org.xujin.halo.command.CommandExecutorI;
+import org.xujin.halo.dto.Response;
+import org.xujin.halo.extension.ExtensionExecutor;
+import org.xujin.halo.logger.Logger;
+import org.xujin.halo.logger.LoggerFactory;
+import org.xujin.halo.test.customer.convertor.CustomerConvertorExtPt;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.xujin.halo.test.customer.validator.extensionpoint.AddCustomerValidatorExtPt;
+import org.xujin.halo.validator.ValidatorExecutor;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * AddCustomerCmdExe
+ *
+ * @author xujin 2018-01-06 7:48 PM
+ */
+@Command
+public class AddCustomerCmdExe implements CommandExecutorI {
+
+ private Logger logger = LoggerFactory.getLogger(AddCustomerCmd.class);
+
+ @Autowired
+ private ValidatorExecutor validatorExecutor;
+
+ @Autowired
+ private ExtensionExecutor extensionExecutor;
+
+
+ @Override
+ public Response execute(AddCustomerCmd cmd) {
+ logger.info("Start processing command:" + cmd);
+ validatorExecutor.validate(AddCustomerValidatorExtPt.class, cmd);
+
+ //Convert CO to Entity
+ CustomerEntity customerEntity = extensionExecutor.execute(CustomerConvertorExtPt.class, extension -> extension.clientToEntity(cmd.getCustomerCO()));
+
+ //Call Domain Entity for business logic processing
+ logger.info("Call Domain Entity for business logic processing..."+customerEntity);
+ customerEntity.addNewCustomer();
+
+ logger.info("End processing command:" + cmd);
+ return Response.buildSuccess();
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/Constants.java b/halo-core/src/test/java/org/xujin/halo/test/customer/Constants.java
new file mode 100644
index 0000000..d42050a
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/Constants.java
@@ -0,0 +1,22 @@
+package org.xujin.halo.test.customer;
+
+/**
+ * Constants
+ *
+ * @author xujin
+ * @date 2018-01-07 1:23 AM
+ */
+public class Constants {
+
+ public static final String BIZ_1 = "BIZ_ONE";
+ public static final String BIZ_2 = "BIZ_TWO";
+
+ public static final String TENANT_ID = "122344";
+
+
+ public static final String SOURCE_AD = "Advertisement";
+ public static final String SOURCE_WB = "Web site";
+ public static final String SOURCE_RFQ = "Request For Quota";
+ public static final String SOURCE_MARKETING = "Marketing";
+ public static final String SOURCE_OFFLINE = "Off Line";
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerCO.java b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerCO.java
new file mode 100644
index 0000000..cf73126
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerCO.java
@@ -0,0 +1,21 @@
+package org.xujin.halo.test.customer;
+
+import org.xujin.halo.dto.ClientObject;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * CustomerCO
+ *
+ * @author xujin 2018-01-06 7:30 PM
+ */
+@Data
+public class CustomerCO extends ClientObject{
+
+ @NotEmpty
+ private String companyName;
+ @NotEmpty
+ private String source; //advertisement, p4p, RFQ, ATM
+ private CustomerType customerType; //potential, intentional, important, vip
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerDO.java b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerDO.java
new file mode 100644
index 0000000..cfaf8d8
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerDO.java
@@ -0,0 +1,22 @@
+package org.xujin.halo.test.customer;
+
+import org.xujin.halo.repository.DataObject;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * CustomerDO
+ *
+ * @author xujin
+ * @date 2018-01-08 1:45 PM
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class CustomerDO implements DataObject {
+ private String customerId;
+ private String memberId;
+ private String globalId;
+ private String companyName;
+ private String source;
+ private String companyType;
+}
\ No newline at end of file
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerServiceI.java b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerServiceI.java
new file mode 100644
index 0000000..ca1f90d
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerServiceI.java
@@ -0,0 +1,14 @@
+package org.xujin.halo.test.customer;
+
+import org.xujin.halo.dto.Response;
+import org.xujin.halo.dto.SingleResponse;
+
+/**
+ * CustomerServiceI
+ *
+ * @author xujin 2018-01-06 7:24 PM
+ */
+public interface CustomerServiceI {
+ public Response addCustomer(AddCustomerCmd addCustomerCmd);
+ public SingleResponse getCustomer(GetOneCustomerQry getOneCustomerQry);
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerServiceImpl.java b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerServiceImpl.java
new file mode 100644
index 0000000..45496be
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerServiceImpl.java
@@ -0,0 +1,29 @@
+package org.xujin.halo.test.customer;
+
+import org.xujin.halo.command.CommandBusI;
+import org.xujin.halo.dto.Response;
+import org.xujin.halo.dto.SingleResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * CustomerServiceImpl
+ *
+ * @author xujin 2018-01-06 7:40 PM
+ */
+@Service
+public class CustomerServiceImpl implements CustomerServiceI{
+
+ @Autowired
+ private CommandBusI commandBus;
+
+ @Override
+ public Response addCustomer(AddCustomerCmd addCustomerCmd) {
+ return (Response)commandBus.send(addCustomerCmd);
+ }
+
+ @Override
+ public SingleResponse getCustomer(GetOneCustomerQry getOneCustomerQry) {
+ return null;
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerType.java b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerType.java
new file mode 100644
index 0000000..69fee9a
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/CustomerType.java
@@ -0,0 +1,13 @@
+package org.xujin.halo.test.customer;
+
+/**
+ * CustomerType
+ *
+ * @author xujin 2018-01-06 7:35 PM
+ */
+public enum CustomerType {
+ POTENTIAL,
+ INTENTIONAL,
+ IMPORTANT,
+ VIP;
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/GetOneCustomerQry.java b/halo-core/src/test/java/org/xujin/halo/test/customer/GetOneCustomerQry.java
new file mode 100644
index 0000000..58dca2f
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/GetOneCustomerQry.java
@@ -0,0 +1,15 @@
+package org.xujin.halo.test.customer;
+
+import org.xujin.halo.dto.Query;
+import lombok.Data;
+
+/**
+ * GetOneCustomerQry
+ *
+ * @author xujin 2018-01-06 7:38 PM
+ */
+@Data
+public class GetOneCustomerQry extends Query{
+ private long customerId;
+ private String companyName;
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerBizOneConvertorExt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerBizOneConvertorExt.java
new file mode 100644
index 0000000..cc9cc68
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerBizOneConvertorExt.java
@@ -0,0 +1,35 @@
+package org.xujin.halo.test.customer.convertor;
+
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.test.customer.Constants;
+import org.xujin.halo.test.customer.CustomerCO;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.xujin.halo.test.customer.entity.SourceType;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * CustomerBizOneConvertorExt
+ *
+ * @author xujin
+ * @date 2018-01-07 3:05 AM
+ */
+@Extension(bizCode = Constants.BIZ_1)
+public class CustomerBizOneConvertorExt implements CustomerConvertorExtPt{
+
+ @Autowired
+ private CustomerConvertor customerConvertor;//Composite basic convertor to do basic conversion
+
+ @Override
+ public CustomerEntity clientToEntity(CustomerCO customerCO){
+ CustomerEntity customerEntity = customerConvertor.clientToEntity(customerCO);
+ //In this business, AD and RFQ are regarded as different source
+ if(Constants.SOURCE_AD.equals(customerCO.getSource()))
+ {
+ customerEntity.setSourceType(SourceType.AD);
+ }
+ if (Constants.SOURCE_RFQ.equals(customerCO.getSource())){
+ customerEntity.setSourceType(SourceType.RFQ);
+ }
+ return customerEntity;
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerBizTwoConvertorExt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerBizTwoConvertorExt.java
new file mode 100644
index 0000000..9202655
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerBizTwoConvertorExt.java
@@ -0,0 +1,33 @@
+package org.xujin.halo.test.customer.convertor;
+
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.test.customer.Constants;
+import org.xujin.halo.test.customer.CustomerCO;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.xujin.halo.test.customer.entity.SourceType;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * CustomerBizTwoConvertorExt
+ *
+ * @author xujin
+ * @date 2018-01-07 3:05 AM
+ */
+@Extension(bizCode = Constants.BIZ_2)
+public class CustomerBizTwoConvertorExt implements CustomerConvertorExtPt{
+
+ @Autowired
+ private CustomerConvertor customerConvertor;//Composite basic convertor to do basic conversion
+
+ @Override
+ public CustomerEntity clientToEntity(CustomerCO customerCO){
+ CustomerEntity customerEntity = customerConvertor.clientToEntity(customerCO);
+ //In this business, if customers from RFQ and Advertisement are both regarded as Advertisement
+ if(Constants.SOURCE_AD.equals(customerCO.getSource()) || Constants.SOURCE_RFQ.equals(customerCO.getSource()))
+ {
+ customerEntity.setSourceType(SourceType.AD);
+ }
+ return customerEntity;
+ }
+
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerConvertor.java b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerConvertor.java
new file mode 100644
index 0000000..a0cfbc7
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerConvertor.java
@@ -0,0 +1,29 @@
+package org.xujin.halo.test.customer.convertor;
+
+import org.xujin.halo.common.ApplicationContextHelper;
+import org.xujin.halo.convertor.ConvertorI;
+import org.xujin.halo.test.customer.CustomerCO;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * CustomerConvertor
+ *
+ * @author xujin
+ * @date 2018-01-07 3:08 AM
+ */
+@Component
+public class CustomerConvertor implements ConvertorI {
+
+ public CustomerEntity clientToEntity(Object clientObject){
+ CustomerCO customerCO = (CustomerCO)clientObject;
+ CustomerEntity customerEntity = (CustomerEntity)ApplicationContextHelper.getBean(CustomerEntity.class);
+ customerEntity.setCompanyName(customerCO.getCompanyName());
+ customerEntity.setCustomerType(customerCO.getCustomerType());
+ return customerEntity;
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerConvertorExtPt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerConvertorExtPt.java
new file mode 100644
index 0000000..5728b53
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/convertor/CustomerConvertorExtPt.java
@@ -0,0 +1,17 @@
+package org.xujin.halo.test.customer.convertor;
+
+import org.xujin.halo.convertor.ConvertorI;
+import org.xujin.halo.extension.ExtensionPointI;
+import org.xujin.halo.test.customer.CustomerCO;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+
+/**
+ * CustomerConvertorExtPt
+ *
+ * @author xujin
+ * @date 2018-01-07 2:37 AM
+ */
+public interface CustomerConvertorExtPt extends ConvertorI, ExtensionPointI {
+
+ public CustomerEntity clientToEntity(CustomerCO customerCO);
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/entity/CustomerEntity.java b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/CustomerEntity.java
new file mode 100644
index 0000000..1b68b9e
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/CustomerEntity.java
@@ -0,0 +1,45 @@
+package org.xujin.halo.test.customer.entity;
+
+import org.xujin.halo.domain.Entity;
+import org.xujin.halo.extension.ExtensionExecutor;
+import org.xujin.halo.test.customer.CustomerType;
+import org.xujin.halo.test.customer.entity.rule.CustomerRuleExtPt;
+import org.xujin.halo.test.customer.repository.CustomerRepository;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * Customer Entity
+ *
+ * @author xujin
+ * @date 2018-01-07 2:38 AM
+ */
+@Data
+@Component
+@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class CustomerEntity extends Entity {
+
+ private String companyName;
+ private SourceType sourceType;
+ private CustomerType customerType;
+
+ @Autowired
+ private CustomerRepository customerRepository;
+ @Autowired
+ private ExtensionExecutor extensionExecutor;
+
+ public CustomerEntity() {
+
+ }
+
+ public void addNewCustomer() {
+ //Add customer policy
+ extensionExecutor.execute(CustomerRuleExtPt.class, extension -> extension.addCustomerCheck(this));
+
+ //Persist customer
+ customerRepository.persist(this);
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/entity/SourceType.java b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/SourceType.java
new file mode 100644
index 0000000..92760c8
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/SourceType.java
@@ -0,0 +1,13 @@
+package org.xujin.halo.test.customer.entity;
+
+/**
+ * SourceType
+ *
+ * @author xujin
+ * @date 2018-01-07 3:02 AM
+ */
+public enum SourceType {
+ AD, //Advertisement 广告
+ WB, // Web site 网站
+ RFQ; // Request For Quota 询盘
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerBizOneRuleExt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerBizOneRuleExt.java
new file mode 100644
index 0000000..dc458d5
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerBizOneRuleExt.java
@@ -0,0 +1,25 @@
+package org.xujin.halo.test.customer.entity.rule;
+
+import org.xujin.halo.exception.BizException;
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.test.customer.Constants;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.xujin.halo.test.customer.entity.SourceType;
+
+/**
+ * CustomerBizOneRuleExt
+ *
+ * @author xujin
+ * @date 2018-01-07 12:10 PM
+ */
+@Extension(bizCode = Constants.BIZ_1)
+public class CustomerBizOneRuleExt implements CustomerRuleExtPt{
+
+ @Override
+ public boolean addCustomerCheck(CustomerEntity customerEntity) {
+ if(SourceType.AD == customerEntity.getSourceType()){
+ throw new BizException("Sorry, Customer from advertisement can not be added in this period");
+ }
+ return true;
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerBizTwoRuleExt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerBizTwoRuleExt.java
new file mode 100644
index 0000000..091f3e2
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerBizTwoRuleExt.java
@@ -0,0 +1,21 @@
+package org.xujin.halo.test.customer.entity.rule;
+
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.test.customer.Constants;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+
+/**
+ * CustomerBizTwoRuleExt
+ *
+ * @author xujin
+ * @date 2018-01-07 12:10 PM
+ */
+@Extension(bizCode = Constants.BIZ_2)
+public class CustomerBizTwoRuleExt implements CustomerRuleExtPt{
+
+ @Override
+ public boolean addCustomerCheck(CustomerEntity customerEntity) {
+ //Any Customer can be added
+ return true;
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerRuleExtPt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerRuleExtPt.java
new file mode 100644
index 0000000..163efba
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/entity/rule/CustomerRuleExtPt.java
@@ -0,0 +1,22 @@
+package org.xujin.halo.test.customer.entity.rule;
+
+import org.xujin.halo.extension.ExtensionPointI;
+import org.xujin.halo.rule.RuleI;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+
+/**
+ * CustomerRuleExtPt
+ *
+ * @author xujin
+ * @date 2018-01-07 12:03 PM
+ */
+public interface CustomerRuleExtPt extends RuleI, ExtensionPointI {
+
+ //Different business check for different biz
+ public boolean addCustomerCheck(CustomerEntity customerEntity);
+
+ //Different upgrade policy for different biz
+ default public void customerUpgradePolicy(CustomerEntity customerEntity){
+ //Nothing special
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/interceptor/ContextInterceptor.java b/halo-core/src/test/java/org/xujin/halo/test/customer/interceptor/ContextInterceptor.java
new file mode 100644
index 0000000..7d16a6e
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/interceptor/ContextInterceptor.java
@@ -0,0 +1,20 @@
+package org.xujin.halo.test.customer.interceptor;
+
+import org.xujin.halo.command.CommandInterceptorI;
+import org.xujin.halo.command.PreInterceptor;
+import org.xujin.halo.dto.Command;
+
+/**
+ * ContextInterceptor
+ *
+ * @author xujin
+ * @date 2018-01-07 1:21 AM
+ */
+@PreInterceptor
+public class ContextInterceptor implements CommandInterceptorI {
+
+ @Override
+ public void preIntercept(Command command) {
+ // TenantContext.set(Constants.TENANT_ID, Constants.BIZ_1);
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/interceptor/ValidationInterceptor.java b/halo-core/src/test/java/org/xujin/halo/test/customer/interceptor/ValidationInterceptor.java
new file mode 100644
index 0000000..7e58f44
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/interceptor/ValidationInterceptor.java
@@ -0,0 +1,36 @@
+package org.xujin.halo.test.customer.interceptor;
+
+import org.xujin.halo.command.CommandInterceptorI;
+import org.xujin.halo.command.PreInterceptor;
+import org.xujin.halo.dto.Command;
+import org.xujin.halo.exception.ParamException;
+import org.xujin.halo.validator.HaloMessageInterpolator;
+import org.hibernate.validator.HibernateValidator;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import java.util.Set;
+
+/**
+ * ValidationInterceptor
+ *
+ * @author xujin 2018-01-06 8:27 PM
+ */
+@PreInterceptor
+public class ValidationInterceptor implements CommandInterceptorI {
+
+ //Enable fail fast, which will improve performance
+ private ValidatorFactory factory = Validation.byProvider(HibernateValidator.class).configure().failFast(true)
+ .messageInterpolator(new HaloMessageInterpolator()).buildValidatorFactory();
+
+ @Override
+ public void preIntercept(Command command) {
+ Validator validator = factory.getValidator();
+ Set> constraintViolations = validator.validate(command);
+ constraintViolations.forEach(violation -> {
+ throw new ParamException(violation.getPropertyPath() + " " + violation.getMessage());
+ });
+ }
+}
\ No newline at end of file
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/repository/CustomerRepository.java b/halo-core/src/test/java/org/xujin/halo/test/customer/repository/CustomerRepository.java
new file mode 100644
index 0000000..d51e54b
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/repository/CustomerRepository.java
@@ -0,0 +1,19 @@
+package org.xujin.halo.test.customer.repository;
+
+import org.xujin.halo.repository.RepositoryI;
+import org.xujin.halo.test.customer.entity.CustomerEntity;
+import org.springframework.stereotype.Repository;
+
+/**
+ * CustomerRepository
+ *
+ * @author xujin
+ * @date 2018-01-07 11:59 AM
+ */
+@Repository
+public class CustomerRepository implements RepositoryI {
+
+ public void persist(CustomerEntity customerEntity){
+ System.out.println("Persist customer to DB : "+ customerEntity);
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extension/AddCustomerBizOneValidator.java b/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extension/AddCustomerBizOneValidator.java
new file mode 100644
index 0000000..5027245
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extension/AddCustomerBizOneValidator.java
@@ -0,0 +1,26 @@
+package org.xujin.halo.test.customer.validator.extension;
+
+import org.xujin.halo.exception.BizException;
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.test.customer.AddCustomerCmd;
+import org.xujin.halo.test.customer.Constants;
+import org.xujin.halo.test.customer.CustomerType;
+import org.xujin.halo.test.customer.validator.extensionpoint.AddCustomerValidatorExtPt;
+
+/**
+ * AddCustomerBizOneValidator
+ *
+ * @author xujin
+ * @date 2018-01-07 1:31 AM
+ */
+@Extension(bizCode = Constants.BIZ_1)
+public class AddCustomerBizOneValidator implements AddCustomerValidatorExtPt {
+
+ @Override
+ public void validate(Object candidate) {
+ AddCustomerCmd addCustomerCmd = (AddCustomerCmd) candidate;
+ //For BIZ TWO CustomerTYpe could not be VIP
+ if(CustomerType.VIP == addCustomerCmd.getCustomerCO().getCustomerType())
+ throw new BizException("Customer Type could not be VIP for Biz One");
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extension/AddCustomerBizTwoValidator.java b/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extension/AddCustomerBizTwoValidator.java
new file mode 100644
index 0000000..8c8764f
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extension/AddCustomerBizTwoValidator.java
@@ -0,0 +1,25 @@
+package org.xujin.halo.test.customer.validator.extension;
+
+import org.xujin.halo.exception.ParamException;
+import org.xujin.halo.extension.Extension;
+import org.xujin.halo.test.customer.AddCustomerCmd;
+import org.xujin.halo.test.customer.Constants;
+import org.xujin.halo.test.customer.validator.extensionpoint.AddCustomerValidatorExtPt;
+
+/**
+ * AddCustomerBizTwoValidator
+ *
+ * @author xujin
+ * @date 2018-01-07 1:31 AM
+ */
+@Extension(bizCode = Constants.BIZ_2)
+public class AddCustomerBizTwoValidator implements AddCustomerValidatorExtPt {
+
+ @Override
+ public void validate(Object candidate) {
+ AddCustomerCmd addCustomerCmd = (AddCustomerCmd) candidate;
+ //For BIZ TWO CustomerTYpe could not be null
+ if (addCustomerCmd.getCustomerCO().getCustomerType() == null)
+ throw new ParamException("CustomerType could not be null");
+ }
+}
diff --git a/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extensionpoint/AddCustomerValidatorExtPt.java b/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extensionpoint/AddCustomerValidatorExtPt.java
new file mode 100644
index 0000000..8b2f642
--- /dev/null
+++ b/halo-core/src/test/java/org/xujin/halo/test/customer/validator/extensionpoint/AddCustomerValidatorExtPt.java
@@ -0,0 +1,14 @@
+package org.xujin.halo.test.customer.validator.extensionpoint;
+
+import org.xujin.halo.extension.ExtensionPointI;
+import org.xujin.halo.validator.ValidatorI;
+
+/**
+ * AddCustomerValidatorExtPt
+ *
+ * @author xujin
+ * @date 2018-01-07 1:27 AM
+ */
+public interface AddCustomerValidatorExtPt extends ValidatorI, ExtensionPointI {
+
+}
diff --git a/halo-core/src/test/resources/sample.properties b/halo-core/src/test/resources/sample.properties
new file mode 100644
index 0000000..9c24605
--- /dev/null
+++ b/halo-core/src/test/resources/sample.properties
@@ -0,0 +1 @@
+bizCode=BIZ_ONE
diff --git a/halo-event/pom.xml b/halo-event/pom.xml
new file mode 100644
index 0000000..d97a655
--- /dev/null
+++ b/halo-event/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+
+ org.xujin.halo
+ halo
+ 1.0.4
+ ../pom.xml
+
+
+ halo-event
+ halo-event
+
+
+
+ org.xujin.halo
+ halo-common
+ ${project.parent.version}
+
+
+ org.springframework
+ spring-context
+ provided
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ provided
+ true
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
diff --git a/halo-event/src/main/java/org/xujin/halo/event/EventPublisher.java b/halo-event/src/main/java/org/xujin/halo/event/EventPublisher.java
new file mode 100644
index 0000000..ef5e4ff
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/EventPublisher.java
@@ -0,0 +1,15 @@
+package org.xujin.halo.event;
+
+/**
+ * 事件发布器
+ */
+public interface EventPublisher {
+
+ /**
+ * 发布事件
+ *
+ * @param event 事件
+ */
+ void publish(Object event);
+
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/annotation/BizListener.java b/halo-event/src/main/java/org/xujin/halo/event/annotation/BizListener.java
new file mode 100644
index 0000000..babec15
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/annotation/BizListener.java
@@ -0,0 +1,24 @@
+package org.xujin.halo.event.annotation;
+
+import org.xujin.halo.event.annotation.listener.Listener;
+import org.xujin.halo.event.extension.support.BizListenerType;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * 业务监听器
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Listener(type = BizListenerType.class)
+public @interface BizListener {
+
+ /**
+ * 优先级
+ * (具体执行顺序需要结合@Listen注解的priorityAsc属性共同决定)
+ */
+ @AliasFor(annotation = Listener.class, attribute = "priority")
+ int priority() default Integer.MAX_VALUE;
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/annotation/Listen.java b/halo-event/src/main/java/org/xujin/halo/event/annotation/Listen.java
new file mode 100644
index 0000000..930dd22
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/annotation/Listen.java
@@ -0,0 +1,25 @@
+package org.xujin.halo.event.annotation;
+
+import org.xujin.halo.event.extension.support.ClassListenResolver;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * 监听
+ */
+@Documented
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@org.xujin.halo.event.annotation.listener.Listen(resolver = ClassListenResolver.class)
+public @interface Listen {
+
+ /**
+ * 是否按照优先级升序
+ *
+ * true:表示升序,即监听器中优先级值越小优先级越高;false:表示降序,即监听器中优先级值越大优先级越高。默认为升序。
+ * 当一个事件发布时,总是先执行完优先级为升序的监听方法,再执行优先级为降序的监听方法
+ */
+ @AliasFor(annotation = org.xujin.halo.event.annotation.listener.Listen.class, attribute = "priorityAsc")
+ boolean priorityAsc() default true;
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/annotation/listener/Listen.java b/halo-event/src/main/java/org/xujin/halo/event/annotation/listener/Listen.java
new file mode 100644
index 0000000..f910b5b
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/annotation/listener/Listen.java
@@ -0,0 +1,28 @@
+
+package org.xujin.halo.event.annotation.listener;
+
+import org.xujin.halo.event.extension.ListenResolver;
+
+import java.lang.annotation.*;
+
+/**
+ * 监听
+ */
+@Documented
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Listen {
+
+ /**
+ * 监听解决器
+ */
+ Class extends ListenResolver> resolver();
+
+ /**
+ * 是否按照优先级升序
+ *
+ * true:表示升序,即监听器中优先级值越小优先级越高;false:表示降序,即监听器中优先级值越大优先级越高。默认为升序。
+ * 当一个事件发布时,总是先执行完优先级为升序的监听方法,再执行优先级为降序的监听方法
+ */
+ boolean priorityAsc() default true;
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/annotation/listener/Listener.java b/halo-event/src/main/java/org/xujin/halo/event/annotation/listener/Listener.java
new file mode 100644
index 0000000..4cc809f
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/annotation/listener/Listener.java
@@ -0,0 +1,27 @@
+package org.xujin.halo.event.annotation.listener;
+
+import org.xujin.halo.event.extension.ListenerType;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * 监听器
+ */
+@Documented
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Component
+public @interface Listener {
+
+ /**
+ * 类型
+ */
+ Class extends ListenerType> type();
+
+ /**
+ * 优先级
+ * (具体执行顺序需要结合@Listen注解的priorityAsc属性共同决定)
+ */
+ int priority() default Integer.MAX_VALUE;
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/boot/EventBusAutoConfiguration.java b/halo-event/src/main/java/org/xujin/halo/event/boot/EventBusAutoConfiguration.java
new file mode 100644
index 0000000..f6657da
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/boot/EventBusAutoConfiguration.java
@@ -0,0 +1,14 @@
+package org.xujin.halo.event.boot;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * 事件总线自动配置类
+ */
+@Configuration
+@Import(EventBusConfiguration.class)
+public class EventBusAutoConfiguration {
+ // 事件总线由EventBusConfiguration进行配置
+ // 本配置类的作用就是在spring-boot项目中自动导入EventBusConfiguration
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/boot/EventBusConfiguration.java b/halo-event/src/main/java/org/xujin/halo/event/boot/EventBusConfiguration.java
new file mode 100644
index 0000000..4f29cf9
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/boot/EventBusConfiguration.java
@@ -0,0 +1,27 @@
+package org.xujin.halo.event.boot;
+
+import org.xujin.halo.event.EventPublisher;
+import org.xujin.halo.event.bus.EventBusesHolder;
+import org.xujin.halo.event.extension.support.BizListenerType;
+import org.xujin.halo.event.listener.ListenersHolder;
+import org.xujin.halo.event.publisher.DefaultEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.context.annotation.Import;
+
+/**
+ * 事件总线配置类
+ * (非spring-boot项目需手动引入本配置类完成事件总线配置)
+ */
+@Configuration
+@Import({EventBusesHolder.class, ListenersHolder.class})
+public class EventBusConfiguration {
+
+ // 业务事件发布器
+ @Bean
+ @DependsOn("EventBusesHolder") // 保证出现循环引用时不会出错
+ public EventPublisher eventPublisher(EventBusesHolder eventBusesHolder) {
+ return new DefaultEventPublisher(eventBusesHolder.getEventBus(BizListenerType.class));
+ }
+}
diff --git a/halo-event/src/main/java/org/xujin/halo/event/bus/EventBus.java b/halo-event/src/main/java/org/xujin/halo/event/bus/EventBus.java
new file mode 100644
index 0000000..fda61ec
--- /dev/null
+++ b/halo-event/src/main/java/org/xujin/halo/event/bus/EventBus.java
@@ -0,0 +1,82 @@
+package org.xujin.halo.event.bus;
+
+import org.xujin.halo.event.extension.EventTypeResolver;
+import org.xujin.halo.event.listener.ListenerExecutor;
+
+import java.util.*;
+
+/**
+ * 事件总线
+ */
+public class EventBus {
+ // 监听器执行器
+ private List listenerExecutors = new ArrayList<>();
+ // 监听器执行器缓存(key:事件类型)
+ private Map