diff --git a/doc/manual/faq/use_nutz_without_other_jar.man b/doc/manual/faq/use_nutz_without_other_jar.man index 3cd2110dd..310618c82 100644 --- a/doc/manual/faq/use_nutz_without_other_jar.man +++ b/doc/manual/faq/use_nutz_without_other_jar.man @@ -13,22 +13,20 @@ 解答 # Nutz的源码中,依赖两个外部jar -- servlet-api.jar 和 log4j.jar # 前者在任何标准J2EE Web应用中都有, Nutz仅支持Servlet 2.4或以上. - # 后者是极其常见的Log4j, 编译器依赖于1.2.14版,但在运行时仅要求是1.2.x. 不过,我推荐最新的log4j 1.2.16 . + # 后者是极其常见的Log4j, 编译器依赖于1.2.17版,但在运行时仅要求是1.2.x. # 我们使用Nutz.Plugin,通过检测是否存在Log4j的核心接口 org.apache.log4j.Log是否存在来判断是否将日志输出到Log4j # 由于仅检测是否存在org.apache.log4j.Log接口,这样就导致两个情况: # 仅仅把Log4j的jar放进classpath,而没有配置之,那么输出日志时,log4j会警告你. 我们认为这是你的失误. # 这样也提供了一个自由度,因为slf4j的log4j-over-slf4j桥也提供这个接口. - 换句话说,如果你把log4j-over-slf4j.jar放进classpath,那么Nutz的日志也能通过Slf4j输出去. + 换句话说,如果你把log4j-over-slf4j.jar放进classpath,那么Nutz的日志也能通过Slf4j输出去. 搭配组合 # 最精简搭配,适合尝试阶段的新手,默认输出的就是Debug信息,完全无鸭梨!! * nutz.jar - # 经典搭配,适合大部分人的需要 - * nutz.jar + log4j-1.2.17.jar # 推荐搭配,搭配最新的log4j * nutz.jar + log4j-1.2.17.jar # 性能至上,使用Logback - * nutz.jar + log4j-over-slf4j.jar + slf4j-api.jar + logback.jar + * nutz.jar + log4j-over-slf4j.jar + slf4j-api.jar + logback-core.jar + logback-classic.jar # 传统选择,使用JDK Loggging * nutz.jar + log4j-over-slf4j.jar + slf4j-api.jar + slfj4-jdk.jar # 另类之选,使用Apache Common Logging diff --git a/doc/manual/integration/daocache.man b/doc/manual/integration/daocache.man index a95d4ff12..252153d4c 100644 --- a/doc/manual/integration/daocache.man +++ b/doc/manual/integration/daocache.man @@ -7,4 +7,4 @@ 请直接看看项目的README,不会让你失望的!! - https://github.com/nutzam/nutzmore/tree/master/nutz-plugins-daocache \ No newline at end of file + [https://github.com/nutzam/nutzmore/tree/master/nutz-plugins-daocache nutz-plugins-daocache] diff --git a/doc/manual/integration/ehcache.man b/doc/manual/integration/ehcache.man index 9ebc4628d..52dfd6ee9 100644 --- a/doc/manual/integration/ehcache.man +++ b/doc/manual/integration/ehcache.man @@ -10,7 +10,7 @@ ----------------------------------------------- 准备工作 - 下载ehcache http://ehcache.org/downloads/catalog + 下载[http://ehcache.org/downloads/catalog ehcache] 你需要一个js文件 -------------------------------------------------- diff --git a/doc/manual/integration/freemarker.man b/doc/manual/integration/freemarker.man index ded90c040..fbc560a77 100644 --- a/doc/manual/integration/freemarker.man +++ b/doc/manual/integration/freemarker.man @@ -13,7 +13,7 @@ org.nutz nutz-plugins-views - 1.b.52.preview + 1.r.57 }}} diff --git a/doc/manual/integration/nutzmore.man b/doc/manual/integration/nutzmore.man index bb79aeb7f..aa9e7fc46 100644 --- a/doc/manual/integration/nutzmore.man +++ b/doc/manual/integration/nutzmore.man @@ -5,7 +5,7 @@ ------------------------------------------------------------------------ 项目概述 - 官网及源码地址 https://github.com/nutzam/nutzmore + 官网及源码地址 [https://github.com/nutzam/nutzmore nutzmore] 为Nutz与其他框架的集成提供官方支持 @@ -22,4 +22,4 @@ nutzmore下的项目的代码量很少,建议先浏览源码熟悉一下逻辑 - 第三方集成是技术活, 需要对nutz与第三方框架都有一定的了解哦 \ No newline at end of file + 第三方集成是技术活, 需要对nutz与第三方框架都有一定的了解哦 diff --git a/doc/manual/integration/quartz.man b/doc/manual/integration/quartz.man index 8b5e0af36..5492bf823 100644 --- a/doc/manual/integration/quartz.man +++ b/doc/manual/integration/quartz.man @@ -7,4 +7,4 @@ 请直接看看项目的README,不会让你失望的!! - https://github.com/nutzam/nutzmore/tree/master/nutz-integration-quartz \ No newline at end of file + [https://github.com/nutzam/nutzmore/tree/master/nutz-integration-quartz nutz-integration-quartz] diff --git a/doc/manual/integration/shiro.man b/doc/manual/integration/shiro.man index de7e5c10f..b48a4ec9e 100644 --- a/doc/manual/integration/shiro.man +++ b/doc/manual/integration/shiro.man @@ -7,4 +7,4 @@ 请直接看看项目的README,不会让你失望的!! - https://github.com/nutzam/nutzmore/tree/master/nutz-integration-shiro \ No newline at end of file + [https://github.com/nutzam/nutzmore/tree/master/nutz-integration-shiro nutz-integration-shiro] diff --git a/doc/manual/integration/velocity.man b/doc/manual/integration/velocity.man index d5104936f..52fcb2ea7 100644 --- a/doc/manual/integration/velocity.man +++ b/doc/manual/integration/velocity.man @@ -13,7 +13,7 @@ org.nutz nutz-plugins-views - 1.b.52.preview + 1.b.57 }}} diff --git a/doc/manual/ioc/ioc_loader.man b/doc/manual/ioc/ioc_loader.man index b7ffa7f7d..e8441239d 100644 --- a/doc/manual/ioc/ioc_loader.man +++ b/doc/manual/ioc/ioc_loader.man @@ -19,7 +19,7 @@ { "对象名称" : { type : "对象类型", - parent : "被继承的对象名称", + parent : "被继承的对象名称", events : { fetch : "触发器的类型或者函数名", create : "触发器的类型或者函数名", @@ -60,7 +60,7 @@ parent : 'bean', // 不声明,默认为 true - singleton : true, + singleton : true, // 这里声明对象的三种事件。事件的处理函数可以是一个接口,也可以是自身的 // 一个方法。 @@ -131,31 +131,31 @@ 在 org.nutz.ioc.meta.IocObject 类中,你如果拿到它的源代码,或者是 JDoc,它描述了在容器中一个对象的全部 信息,你会发现它其实也简单: - {{{ - public class IocObject { + {{{ + public class IocObject { // 对象的 Java 类型 - private Class type; + private Class type; // 声明对象是否为单例 - private boolean singleton; + private boolean singleton; // 对象监听何种事件,对应有 // "fetch" - 每次对象被 ioc.get 的时候,触发 // "create" - 当且仅当对象被 new 的时候触发 // "depose" - 当对象被容器销毁时触发 - private IocEventSet events; + private IocEventSet events; // 对象构造函数的参数列表 - private List args; + private List args; // 对象的字段 - private List fields; + private List fields; // 对象的缓存范围,默认为 "app" - private String scope; - - // 省略所有的 getter 和 setter 函数 + private String scope; + + // 省略所有的 getter 和 setter 函数 }}} 事件集合 IocEventSet @@ -199,7 +199,7 @@ 你的 IocLoader 的实现类需要实现三个方法: - {{{ + {{{ public interface IocLoader { /** diff --git a/doc/manual/ioc/ioc_properties.man b/doc/manual/ioc/ioc_properties.man index 78d157ef4..9c231c356 100644 --- a/doc/manual/ioc/ioc_properties.man +++ b/doc/manual/ioc/ioc_properties.man @@ -134,7 +134,7 @@ 如果是这样,那么 @Inject 可以这样写: {{{ - @Inject("java:$config.get('xxxxx') + @Inject("java:$config.get('xxxxx')") private String myXXXX; }}} diff --git a/doc/manual/ioc/loader_combo.man b/doc/manual/ioc/loader_combo.man index 4b4294c5d..b6cb4b9b3 100644 --- a/doc/manual/ioc/loader_combo.man +++ b/doc/manual/ioc/loader_combo.man @@ -38,16 +38,15 @@ 复合加载器非常简单,似乎只花了 Wendal 同学不到 1 个小时的时间,因为它本身并不做任何 事情,它只是调用其他的加载器: {{{ - @IocBy(type=ComboIocLoader.class, args={ - "*js", - "ioc/dao.js", - "ioc/service.js", - "*anno", - "com.myapp.module", - "com.myapp.service", - "*tx", - "*async" // @Async注解,异步执行. - }) + ComboIocLoader loader = new ComboIocLoader("*js", + "ioc/dao.js", + "ioc/service.js", + "*anno", + "com.myapp.module", + "com.myapp.service", + "*tx", + // @Async注解,异步执行. + "*async"); }}} 如上面的例子,组合加载器,组合了3个 Ioc 加载器,一个是 JsonLoader,一个是 AnnotationIocLoader, 一个是AOP事务的TransIocLoader(1.b.52新增)。 diff --git a/doc/manual/lang/mirror.man b/doc/manual/lang/mirror.man index 9286f0dc2..3986fca73 100644 --- a/doc/manual/lang/mirror.man +++ b/doc/manual/lang/mirror.man @@ -203,3 +203,8 @@ mirror.setValue(obj, "name", "XiaoBai"); }}} + 获取静态字段 + * 取静态属性 + {{{ + Field[] fields = mirror.getStaticField(nofinalOrAll); + }}} \ No newline at end of file diff --git a/src/org/nutz/dao/Chain.java b/src/org/nutz/dao/Chain.java index c257351f6..2dbf70501 100644 --- a/src/org/nutz/dao/Chain.java +++ b/src/org/nutz/dao/Chain.java @@ -272,12 +272,15 @@ public void invoke(MappingField mf, Object val) { //============================================================= /** - * 添加一个特殊节点, 如果value非空,则有3个情况:

+ * 添加一个特殊节点, 如果value非空而且是String类型,则有3个情况:

*

  • +1 效果如age=age+1
  • *
  • -1 效果如count=count-1
  • *
  • 支持的运算符有 + - *\/ % & ^ | - *
  • 其他值, 则对value.toString(),效果如 time=todate("XXXXX")
  • - * + *
  • 其他值, 则对value.toString()
  • + *

    + * Chain chain = Chain.makeSpecial("age", "+1");//输出的SQL会是 age=age+1 + *

    + * Chain chain = Chain.makeSpecial("ct", "now()");//输出的SQL会是 ct=now(),但不建议用依赖特定数据库的now(),仅供演示. * @since 1.b.44 */ public abstract Chain addSpecial(String name, Object value); diff --git a/src/org/nutz/dao/DaoInterceptor.java b/src/org/nutz/dao/DaoInterceptor.java new file mode 100644 index 000000000..9f17e77e2 --- /dev/null +++ b/src/org/nutz/dao/DaoInterceptor.java @@ -0,0 +1,12 @@ +package org.nutz.dao; + +/** + * Dao操作拦截器 + * @author wendal + * @see org.nutz.dao.impl.interceptor.DaoLogInterceptor + * @see org.nutz.dao.impl.interceptor.DaoTimeInterceptor + */ +public interface DaoInterceptor { + + void filter(DaoInterceptorChain chain) throws DaoException; +} diff --git a/src/org/nutz/dao/DaoInterceptorChain.java b/src/org/nutz/dao/DaoInterceptorChain.java new file mode 100644 index 000000000..d3a52ffb2 --- /dev/null +++ b/src/org/nutz/dao/DaoInterceptorChain.java @@ -0,0 +1,226 @@ +package org.nutz.dao; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; + +import org.nutz.dao.impl.DaoExecutor; +import org.nutz.dao.sql.DaoStatement; +import org.nutz.dao.sql.SqlContext; +import org.nutz.lang.random.R; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * Dao执行拦截器链. + * + * @author wendal + * @see org.nutz.dao.impl.interceptor.DaoLogInterceptor + * @see org.nutz.dao.impl.interceptor.DaoTimeInterceptor + */ +public class DaoInterceptorChain implements ConnCallback { + + private static final Log log = Logs.get(); + + protected int autoTransLevel; + + protected Connection connection; + + protected int current = 0; + + protected DaoStatement daoStatement; + + protected DaoExecutor executor; + + protected List interceptors = new ArrayList(); + + protected int updateCount; + + protected DaoStatement[] sts; + + protected String id; + + /** + * 新建一个DaoInterceptorChain. + * + * @param sts + * 将要进行的Dao操作(不一定是SQL操作,有可能是EL) + */ + public DaoInterceptorChain(DaoStatement... sts) { + this.sts = sts; + id = R.UU32(); + } + + /** + * 继续下一个拦截器,如果已经是最后一个拦截器,那么执行executor.exec + * + * @return 本对象,用于链式操作 + * @throws Exception + */ + public DaoInterceptorChain doChain() throws DaoException { + if (hasNext()) { + DaoInterceptor interceptor = next(); + current++; + interceptor.filter(this); + } else { + executor.exec(getConnection(), getDaoStatement()); + updateCount += getDaoStatement().getUpdateCount(); + } + return this; + } + + /** + * 获取当前自动事务级别,DaoRunner中使用强制事务时会使用之.拦截器不能修改,即使修改也不会生效 + * + * @return 当前自动(强制)事务级别 + */ + public int getAutoTransLevel() { + return autoTransLevel; + } + + /** + * 当前执行的DaoStatement + * + * @return 当前执行的DaoStatement + */ + public DaoStatement getDaoStatement() { + return daoStatement; + } + + /** + * 全部DaoStatement,可能不止一条 + * + * @return 全部DaoStatement + */ + public DaoStatement[] getDaoStatements() { + return sts; + } + + /** + * 拦截器列表(暂不开放修改) + * + * @return 全体拦截器列表 + */ + public List getInterceptors() { + return interceptors; + } + + /** + * 更新总数,用于DaoSupport(NutDao)获取更新总数. + * + * @return 更新记录总数 + */ + public int getUpdateCount() { + return updateCount; + } + + /** + * 是否还有下一个拦截器 + * + * @return true,如果还有拦截器要执行 + */ + public boolean hasNext() { + return current < interceptors.size(); + } + + /** + * 这是DaoExecutor会执行的方法,拦截器内不要执行这个方法!! 这里也是拦截器开始生效的地方. + */ + public void invoke(Connection conn) throws Exception { + for (DaoStatement st : sts) { + if (st == null) { + if (log.isInfoEnabled()) + log.info("Found a null DaoStatement(SQL), ingore it ~~"); + continue; + } + daoStatement = st; + this.connection = conn; + doChain(); + } + } + + /** + * 获取下一个拦截器. 调用前必须先调用hasNext进行判断 + * + * @return 下一个拦截器 + */ + public DaoInterceptor next() { + return interceptors.get(current); + } + + /** + * 设置强制事务的级别,对拦截器来说无意义. + * + * @param autoTransLevel + * 与DaoSupport(NutDao)内的值一致 + */ + public void setAutoTransLevel(int autoTransLevel) { + this.autoTransLevel = autoTransLevel; + } + + /** + * 设置当前拦截器索引. 若设置值大于拦截器列表的大小,那么效果就等同于跳过剩余拦截器,直接执行DaoStatement + * + * @param current + */ + public void setCurrent(int current) { + this.current = current; + } + + /** + * 设置DaoExecutor. 典型应用是在拦截器中替换成daocache提供的DaoExecutor + * + * @param executor + * 新的DaoExecutor,不可以是null + */ + public void setExecutor(DaoExecutor executor) { + this.executor = executor; + } + + /** + * 设置新的拦截器列表. + * + * @param interceptors + * 新的拦截器列表 + */ + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } + + /** + * 设置当前使用的数据库连接 + * + * @param connection + * 新的数据库连接,不可以是null + */ + public void setConnection(Connection connection) { + this.connection = connection; + } + + /** + * 获取当前数据库连接 + * + * @return 当前数据库连接 + */ + public Connection getConnection() { + return connection; + } + + /** + * 获取当前DaoStatement的上下文,注意,一个拦截器链可能包含多个DaoStatement + * + * @return 当前DaoStatement的上下文 + */ + public SqlContext getSqlContext() { + return getDaoStatement().getContext(); + } + + /** + * 拦截器链的id, 为一个uu32识别符. + * + * @return 本拦截器链的id + */ + public String getId() { + return id; + } +} diff --git a/src/org/nutz/dao/impl/DaoSupport.java b/src/org/nutz/dao/impl/DaoSupport.java index f72d283d7..fa94b88df 100644 --- a/src/org/nutz/dao/impl/DaoSupport.java +++ b/src/org/nutz/dao/impl/DaoSupport.java @@ -5,16 +5,23 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.sql.DataSource; import org.nutz.dao.ConnCallback; +import org.nutz.dao.DaoInterceptor; +import org.nutz.dao.DaoInterceptorChain; import org.nutz.dao.DatabaseMeta; import org.nutz.dao.SqlManager; import org.nutz.dao.entity.EntityMaker; import org.nutz.dao.impl.entity.AnnotationEntityMaker; +import org.nutz.dao.impl.interceptor.DaoLogInterceptor; +import org.nutz.dao.impl.interceptor.DaoTimeInterceptor; import org.nutz.dao.impl.sql.NutPojoMaker; import org.nutz.dao.impl.sql.run.NutDaoExecutor; import org.nutz.dao.impl.sql.run.NutDaoRunner; @@ -25,12 +32,11 @@ import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.SqlContext; import org.nutz.dao.util.Daos; +import org.nutz.lang.Lang; +import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.log.Log; import org.nutz.log.Logs; -import org.nutz.trans.Atom; -import org.nutz.trans.Trans; -import org.nutz.trans.Transaction; /** * Dao 接口实现类的一些基础环境 @@ -82,10 +88,14 @@ public class DaoSupport { private SqlManager sqlManager; protected int autoTransLevel = Connection.TRANSACTION_READ_COMMITTED; + + protected List _interceptors; public DaoSupport() { this.runner = new NutDaoRunner(); this.executor = new NutDaoExecutor(); + this._interceptors = new ArrayList(); + this.addInterceptor("log"); } /** @@ -258,44 +268,13 @@ public void run(ConnCallback callback) { } protected int _exec(final DaoStatement... sts) { - // 看看是不是都是 SELECT 语句 - boolean isAllSelect = true; - for (DaoStatement st : sts) { - if (!st.isSelect()) { - isAllSelect = false; - break; - } - } - // 这个是具体执行的逻辑,作为一个回调 - // 后面的逻辑是判断到底应不应该在一个事务里运行它 - DaoExec callback = new DaoExec(sts); - - // 如果强制没有事务或者都是 SELECT,没必要启动事务 - boolean useTrans = false; - switch (meta.getType()) { - case PSQL: - useTrans = true; - break; - case SQLITE: - Transaction t = Trans.get(); - useTrans = (t != null && (t.getLevel() == Connection.TRANSACTION_SERIALIZABLE - || t.getLevel() == Connection.TRANSACTION_READ_UNCOMMITTED)); - break; - default: - useTrans = !(Trans.isTransactionNone() && (sts.length==1 || isAllSelect)); - break; - } - if (!useTrans) { - run(callback); - } - // 否则启动事务 - // wendal: 还是很有必要的!!尤其是解决insert的@Prev/@Next不在同一个链接的问题 - else { - Trans.exec(autoTransLevel, callback); - } - + final DaoInterceptorChain callback = new DaoInterceptorChain(sts); + callback.setExecutor(executor); + callback.setAutoTransLevel(autoTransLevel); + callback.setInterceptors(Collections.unmodifiableList(this._interceptors)); + run(callback); // 搞定,返回结果 ^_^ - return callback.re; + return callback.getUpdateCount(); } /** @@ -306,36 +285,6 @@ protected int _exec(final DaoStatement... sts) { protected EntityMaker createEntityMaker() { return new AnnotationEntityMaker(dataSource, expert, holder); } - - /** - * - * @author wendal - * @since 1.b.44 - */ - protected class DaoExec implements Atom, ConnCallback { - private DaoStatement[] sts; - private int re; - - public DaoExec(DaoStatement... sts) { - this.sts = sts; - } - - public void run() { - DaoSupport.this.run(this); - } - - public void invoke(Connection conn) throws Exception { - for (DaoStatement st : sts) { - if (st == null) { - if (log.isInfoEnabled()) - log.info("Found a null DaoStatement(SQL), ingore it ~~"); - continue; - } - executor.exec(conn, st); - re += st.getUpdateCount(); - } - } - } public PojoMaker pojoMaker() { return pojoMaker; @@ -344,4 +293,38 @@ public PojoMaker pojoMaker() { public void setAutoTransLevel(int autoTransLevel) { this.autoTransLevel = autoTransLevel; } + + public void setInterceptors(List interceptors) { + this._interceptors.clear(); + for (Object it : interceptors) { + addInterceptor(it); + } + } + + public void addInterceptor(Object it) { + if (it == null) + return; + if (it instanceof String) { + String itName = it.toString().trim(); + if ("log".equals(itName)) { + this._interceptors.add(new DaoLogInterceptor()); + } + else if ("time".equals(itName)) { + this._interceptors.add(new DaoTimeInterceptor()); + } + else if (itName.contains(".")) { + Class klass = Lang.loadClassQuite(itName); + if (klass == null) { + log.warn("no such interceptor name="+itName); + } else { + this._interceptors.add((DaoInterceptor) Mirror.me(klass).born()); + } + } else { + log.info("unkown interceptor name="+itName); + } + } + else if (it instanceof DaoInterceptor) { + this._interceptors.add((DaoInterceptor) it); + } + } } diff --git a/src/org/nutz/dao/impl/interceptor/DaoLogInterceptor.java b/src/org/nutz/dao/impl/interceptor/DaoLogInterceptor.java new file mode 100644 index 000000000..1e1a72960 --- /dev/null +++ b/src/org/nutz/dao/impl/interceptor/DaoLogInterceptor.java @@ -0,0 +1,25 @@ +package org.nutz.dao.impl.interceptor; + +import org.nutz.dao.DaoException; +import org.nutz.dao.DaoInterceptor; +import org.nutz.dao.DaoInterceptorChain; +import org.nutz.dao.impl.sql.run.NutDaoExecutor; +import org.nutz.dao.sql.DaoStatement; + +/** + * 把DaoStatement优美地打印出来.默认启用. + * + * @author wendal + * @since 1.r.58 + */ +public class DaoLogInterceptor implements DaoInterceptor { + + public void filter(DaoInterceptorChain chain) throws DaoException { + DaoStatement statement = chain.getDaoStatement(); + if (statement != null) { + NutDaoExecutor.printSQL(statement); + } + chain.doChain(); + } + +} diff --git a/src/org/nutz/dao/impl/interceptor/DaoTimeInterceptor.java b/src/org/nutz/dao/impl/interceptor/DaoTimeInterceptor.java new file mode 100644 index 000000000..87df60cbf --- /dev/null +++ b/src/org/nutz/dao/impl/interceptor/DaoTimeInterceptor.java @@ -0,0 +1,34 @@ +package org.nutz.dao.impl.interceptor; + +import org.nutz.dao.DaoException; +import org.nutz.dao.DaoInterceptor; +import org.nutz.dao.DaoInterceptorChain; +import org.nutz.lang.Stopwatch; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * 打印执行耗时. 默认不启用. + * + * @author wendal + * @since 1.r.58 + */ +public class DaoTimeInterceptor implements DaoInterceptor { + + private static final Log log = Logs.get(); + + public void filter(DaoInterceptorChain chain) throws DaoException { + Stopwatch sw = Stopwatch.begin(); + try { + chain.doChain(); + } + finally { + sw.stop(); + if (log.isDebugEnabled()) + log.debugf("time=%sms, sql=%s", + sw.getDuration(), + chain.getDaoStatement().toString()); + } + } + +} diff --git a/src/org/nutz/dao/impl/jdbc/NutPojo.java b/src/org/nutz/dao/impl/jdbc/NutPojo.java index fac4f9e8f..819567654 100644 --- a/src/org/nutz/dao/impl/jdbc/NutPojo.java +++ b/src/org/nutz/dao/impl/jdbc/NutPojo.java @@ -237,8 +237,4 @@ private int _params_count() { } return _pmnum; } - - public String forPrint() { - return this.toString(); - } } diff --git a/src/org/nutz/dao/impl/sql/NutSql.java b/src/org/nutz/dao/impl/sql/NutSql.java index e6f86785b..3197cda9a 100644 --- a/src/org/nutz/dao/impl/sql/NutSql.java +++ b/src/org/nutz/dao/impl/sql/NutSql.java @@ -193,14 +193,6 @@ public String getSourceSql() { return sourceSql; } - public String toString() { - return super.toStatement(this.getParamMatrix(), this.toPreparedStatement()); - } - - public String forPrint() { - return super.toString(); - } - class SqlVarPItem extends AbstractPItem { public String name; diff --git a/src/org/nutz/dao/impl/sql/NutStatement.java b/src/org/nutz/dao/impl/sql/NutStatement.java index fe195072c..786c2f416 100644 --- a/src/org/nutz/dao/impl/sql/NutStatement.java +++ b/src/org/nutz/dao/impl/sql/NutStatement.java @@ -184,7 +184,7 @@ public int getUpdateCount() { return context.getUpdateCount(); } - public String toString() { + public String forPrint() { String sql = this.toPreparedStatement(); StringBuilder sb = new StringBuilder(sql); // 准备打印参数表 @@ -339,8 +339,7 @@ public boolean isForceExecQuery() { return forceExecQuery; } - public String forPrint() { - return super.toString(); + public String toString() { + return toStatement(this.getParamMatrix(), this.toPreparedStatement()); } - } diff --git a/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java b/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java index a2ee198c4..1db06ecb7 100644 --- a/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java +++ b/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java @@ -149,7 +149,7 @@ protected void _runExec(Connection conn, DaoStatement st) throws SQLException { } // NOT support for this yet. by wendal //} else if (stmt.getUpdateCount() > -1) { - // st.onAfter(conn, null); + // st.onAfter(connection, null); } break; } @@ -188,8 +188,6 @@ private void _runSelect(Connection conn, DaoStatement st) // 木有参数,直接运行 if (null == paramMatrix || paramMatrix.length == 0 || paramMatrix[0].length == 0) { - if (log.isDebugEnabled()) - log.debug(st.forPrint()); stat = conn.createStatement(st.getContext() .getResultSetType(), ResultSet.CONCUR_READ_ONLY); if (lastRow > 0) @@ -206,9 +204,6 @@ private void _runSelect(Connection conn, DaoStatement st) if (log.isWarnEnabled()) log.warnf("Drop last %d rows parameters for:\n%s", paramMatrix.length - 1, st); - } - if (log.isDebugEnabled()) { - log.debug(st); } // 准备运行语句 @@ -249,10 +244,6 @@ private void _runPreparedStatement(Connection conn, DaoStatement st, Object[][] String sql = st.toPreparedStatement(); PreparedStatement pstat = null; - // 打印调试信息 - if (log.isDebugEnabled()) - log.debug(st); - try { // 创建 SQL 语句 if (st.getContext().attr("RETURN_GENERATED_KEYS") == null) @@ -312,10 +303,6 @@ private void _runStatement(Connection conn, DaoStatement st) throws SQLException Statement stat = null; String sql = st.toPreparedStatement(); - // 打印调试信息 - if (log.isDebugEnabled()) - log.debug(sql); - try { stat = conn.createStatement(); stat.execute(sql); @@ -344,4 +331,11 @@ public void setMeta(DatabaseMeta meta) { public void setExpert(JdbcExpert expert) { this.expert = expert; } + + // 写在这里完全是为了兼容老版本的log4j配置 + public static void printSQL(DaoStatement sql) { + // 打印调试信息 + if (log.isDebugEnabled()) + log.debug(sql.forPrint()); + } } diff --git a/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java b/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java index a3f95f816..00145b808 100644 --- a/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java +++ b/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java @@ -8,10 +8,13 @@ import org.nutz.dao.ConnCallback; import org.nutz.dao.DaoException; +import org.nutz.dao.DaoInterceptorChain; import org.nutz.dao.DatabaseMeta; import org.nutz.dao.impl.DaoRunner; +import org.nutz.dao.sql.DaoStatement; import org.nutz.log.Log; import org.nutz.log.Logs; +import org.nutz.trans.Atom; import org.nutz.trans.Trans; import org.nutz.trans.Transaction; @@ -23,74 +26,147 @@ public class NutDaoRunner implements DaoRunner { private static final Log log = Logs.get(); - - public void run(DataSource dataSource, ConnCallback callback) { - Transaction t = Trans.get(); - // 有事务 - if (null != t) { - Connection conn = null; - Savepoint sp = null; - try { - conn = t.getConnection(dataSource); - if (meta != null && meta.isPostgresql()) { - sp = conn.setSavepoint(); + + protected DataSource slaveDataSource; + + public void run(final DataSource dataSource, final ConnCallback callback) { + if (callback instanceof DaoInterceptorChain) { + // 看看是不是应该强制使用事务 + DaoStatement[] sts = ((DaoInterceptorChain)callback).getDaoStatements(); + boolean useTrans = false; + boolean isAllSelect = true; + for (DaoStatement st : sts) { + if (!st.isSelect() && !st.isForceExecQuery()) { + isAllSelect = false; + break; } - callback.invoke(conn); } - catch (Exception e) { - if (sp != null) - try { - conn.rollback(sp); - } - catch (SQLException e1) { + switch (meta.getType()) { + case PSQL: + // PSQL必须带事务,不然Clob和Blob操作必死 + useTrans = true; + break; + case SQLITE: + // SQLITE仅支持2种事务级别 + Transaction t = Trans.get(); + useTrans = (t != null && (t.getLevel() == Connection.TRANSACTION_SERIALIZABLE + || t.getLevel() == Connection.TRANSACTION_READ_UNCOMMITTED)); + break; + default: + useTrans = !(Trans.isTransactionNone() && (sts.length==1 || isAllSelect)); + break; + } + // 看来需要开启事务了 + if (useTrans) { + Trans.exec(((DaoInterceptorChain) callback).getAutoTransLevel(), new Atom() { + public void run() { + _run(dataSource, callback); } - if (e instanceof DaoException) - throw (DaoException)e; - throw new DaoException(e); + }); + return; } } + // 不需要额外加事务,直接通过 + _run(dataSource, callback); + } + + public void _run(DataSource dataSource, ConnCallback callback) { + Transaction t = Trans.get(); + // 有事务 + if (null != t) { + _runWithTransaction(t, dataSource, callback); + } // 无事务 else { - Connection conn = null; - // 开始一个连接 - try { - conn = dataSource.getConnection(); - // 开始循环运行 - callback.invoke(conn); - // 完成提交 - if (!conn.getAutoCommit()) - conn.commit(); + _runWithoutTransaction(dataSource, callback); + } + } + + protected void _runWithTransaction(Transaction t, DataSource dataSource, ConnCallback callback) { + Connection conn = null; + Savepoint sp = null; + try { + conn = t.getConnection(selectDataSource(t, dataSource, callback)); + if (meta != null && meta.isPostgresql()) { + sp = conn.setSavepoint(); } - // 异常回滚 - catch (Exception e) { + runCallback(conn, callback); + } + catch (Exception e) { + if (sp != null) try { - if (conn != null) // 高并发时,从数据库连接池获取连接就已经抛错误,所以conn可能为null的 - conn.rollback(); + conn.rollback(sp); } - catch (Exception e1) {}// TODO 简单记录一下? - if (e instanceof DaoException) - throw (DaoException)e; - throw new DaoException(e); + catch (SQLException e1) { + } + if (e instanceof DaoException) + throw (DaoException)e; + throw new DaoException(e); + } + } + + public void _runWithoutTransaction(DataSource dataSource, ConnCallback callback) { + Connection conn = null; + // 开始一个连接 + try { + conn = selectDataSource(null, dataSource, callback).getConnection(); + // 开始真正运行 + runCallback(conn, callback); + // 完成提交 + if (!conn.getAutoCommit()) + conn.commit(); + } + // 异常回滚 + catch (Exception e) { + try { + if (conn != null) // 高并发时,从数据库连接池获取连接就已经抛错误,所以conn可能为null的 + conn.rollback(); } - // 保证释放资源 - finally { - if (null != conn) { - // 关闭链接 - try { - conn.close(); - } - catch (SQLException closeE) { - if (log.isWarnEnabled()) - log.warn("Fail to close connection!", closeE); - } + catch (Exception e1) {}// TODO 简单记录一下? + if (e instanceof DaoException) + throw (DaoException)e; + throw new DaoException(e); + } + // 保证释放资源 + finally { + if (null != conn) { + // 关闭链接 + try { + conn.close(); + } + catch (SQLException closeE) { + if (log.isWarnEnabled()) + log.warn("Fail to close connection!", closeE); } } } } + + + protected void runCallback(Connection conn, ConnCallback callback) throws Exception { + callback.invoke(conn); + } protected DatabaseMeta meta; public void setMeta(DatabaseMeta meta) { this.meta = meta; } + + public void setSlaveDataSource(DataSource slaveDataSource) { + this.slaveDataSource = slaveDataSource; + } + + protected DataSource selectDataSource(Transaction t, DataSource master, ConnCallback callback) { + if (this.slaveDataSource == null) + return master; + if (t == null && callback instanceof DaoInterceptorChain) { + DaoInterceptorChain chain = (DaoInterceptorChain)callback; + DaoStatement[] sts = chain.getDaoStatements(); + if (sts.length == 1 && (sts[0].isSelect() || sts[0].isForceExecQuery())) { + return slaveDataSource; + } + } + return master; + } } diff --git a/src/org/nutz/http/Cookie.java b/src/org/nutz/http/Cookie.java index 3dec27b2e..8035fee6f 100644 --- a/src/org/nutz/http/Cookie.java +++ b/src/org/nutz/http/Cookie.java @@ -44,11 +44,20 @@ public Cookie set(String name, String value) { } public void parse(String str) { + if (debug) + log.debug("parse " + str); String[] ss = Strings.splitIgnoreBlank(str, ";"); for (String s : ss) { Pair p = Pair.create(Strings.trim(s)); - if (p.getValueString() == null || "Path".equals(p.getName())) + if (p.getValueString() == null) + continue; + if ("Path".equals(p.getName()) || "Expires".equals(p.getName())) continue; + if ("Max-Age".equals(p.getName())) { + int age = Integer.parseInt(p.getValue()); + if (age == 0) + return; + } String val = p.getValueString(); if (debug) log.debugf("add cookie [%s=%s]", p.getName(), val); @@ -76,7 +85,8 @@ public void afterConnect(Request request, HttpURLConnection conn) { String c = toString(); if (debug) log.debugf("add Cookie for req [%s]", c); - conn.addRequestProperty("Cookie", c); + if (!Strings.isBlank(c)) + conn.addRequestProperty("Cookie", c); } public void afterResponse(Request request, HttpURLConnection conn, Response response) { diff --git a/src/org/nutz/http/Header.java b/src/org/nutz/http/Header.java index 1b00646a4..a1785bbb1 100644 --- a/src/org/nutz/http/Header.java +++ b/src/org/nutz/http/Header.java @@ -69,8 +69,7 @@ public static Header create() { Header header = new Header(); header.set("User-Agent", "Nutz.Robot"); header.set("Accept-Encoding", "gzip,deflate"); - header.set("Accept", "text/xml,application/xml,application/xhtml+xml,text/html;" - + "q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); + header.set("Accept", "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;"); header.set("Accept-Language", "en-US,en,zh,zh-CN"); header.set("Accept-Charset", "ISO-8859-1,*,utf-8"); header.set("Connection", "keep-alive"); @@ -91,4 +90,14 @@ public int getInt(String key, int defaultValue) { return defaultValue; return Integer.parseInt(value); } + + public Header asJsonContentType() { + set("Content-Type", "application/json"); + return this; + } + + public Header asFormContentType() { + set("Content-Type", "application/x-www-form-urlencoded"); + return this; + } } diff --git a/src/org/nutz/http/Request.java b/src/org/nutz/http/Request.java index d02fff288..da594ea4b 100644 --- a/src/org/nutz/http/Request.java +++ b/src/org/nutz/http/Request.java @@ -143,6 +143,8 @@ public InputStream getInputStream() { throw Lang.wrapThrow(e); } } + if (header.get("Content-Type") == null) + header.asFormContentType(); return new ByteArrayInputStream(data); } } @@ -171,7 +173,7 @@ public Request setData(String data) { return this; } - private Request setParams(Map params) { + public Request setParams(Map params) { this.params = params; return this; } diff --git a/src/org/nutz/http/Response.java b/src/org/nutz/http/Response.java index 507917eda..91cb75ff4 100644 --- a/src/org/nutz/http/Response.java +++ b/src/org/nutz/http/Response.java @@ -23,8 +23,10 @@ public Response(HttpURLConnection conn, Map reHeader) throws IOE detail = conn.getResponseMessage(); this.header = Header.create(reHeader); String s = header.get("Set-Cookie"); - if (null != s) - this.cookie = new Cookie(s); + if (null != s) { + this.cookie = new Cookie(); + this.cookie.afterResponse(null, conn, null); // 解决多个Set-Cookie丢失的问题 + } } private Header header; @@ -73,13 +75,13 @@ public Header getHeader() { public String getEncodeType() { String contextType = header.get("Content-Type"); if (null != contextType) { - for (String tmp : contextType.split(";")) { - if (tmp == null) - continue; - tmp = tmp.trim(); - if (tmp.startsWith("charset=")) - return Strings.trim(tmp.substring(8)).trim(); - } + for (String tmp : contextType.split(";")) { + if (tmp == null) + continue; + tmp = tmp.trim(); + if (tmp.startsWith("charset=")) + return Strings.trim(tmp.substring(8)).trim(); + } } return null; } diff --git a/src/org/nutz/http/Sender.java b/src/org/nutz/http/Sender.java index 103521ceb..1c7842f5e 100644 --- a/src/org/nutz/http/Sender.java +++ b/src/org/nutz/http/Sender.java @@ -1,6 +1,8 @@ package org.nutz.http; import java.io.BufferedInputStream; +import java.io.File; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -20,6 +22,7 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +import org.nutz.http.sender.FilePostSender; import org.nutz.http.sender.GetSender; import org.nutz.http.sender.PostSender; import org.nutz.lang.stream.NullInputStream; @@ -50,18 +53,24 @@ public static Sender create(String url) { } public static Sender create(String url, int timeout) { - return create(Request.get(url)).setTimeout(timeout); + return create(url).setTimeout(timeout); } public static Sender create(Request request) { - return request.isGet() || request.isDelete() ? new GetSender(request) - : new PostSender(request); + if (request.isGet() || request.isDelete()) + return new GetSender(request); + if (request.isPost() || request.isPut()) { + for (Object val : request.getParams().values()) { + if (val instanceof File || val instanceof File[]) { + return new FilePostSender(request); + } + } + } + return new PostSender(request); } public static Sender create(Request request, int timeout) { - Sender sender = request.isGet() || request.isDelete() ? new GetSender(request) - : new PostSender(request); - return sender.setTimeout(timeout); + return create(request).setTimeout(timeout); } protected Request request; @@ -70,13 +79,17 @@ public static Sender create(Request request, int timeout) { protected HttpURLConnection conn; - protected HttpReqRespInterceptor interceptor; + protected HttpReqRespInterceptor interceptor = new Cookie(); protected Callback callback; + + protected boolean followRedirects = true; protected Sender(Request request) { this.request = request; } + + protected Callback progressListener; public abstract Response send() throws HttpException; @@ -135,7 +148,7 @@ protected Map getResponseHeader() throws IOException { protected void setupDoInputOutputFlag() { conn.setDoInput(true); conn.setDoOutput(true); - // conn.setUseCaches(false); + conn.setInstanceFollowRedirects(followRedirects); } protected void openConnection() throws IOException { @@ -255,4 +268,32 @@ public static List shutdown() { public static ExecutorService getExecutorService() { return es; } + + public Sender setFollowRedirects(boolean followRedirects) { + this.followRedirects = followRedirects; + return this; + } + + protected OutputStream getOutputStream() throws IOException { + OutputStream out = conn.getOutputStream(); + if (progressListener == null) + return out; + return new FilterOutputStream(out) { + int count; + public void write(byte[] b, int off, int len) throws IOException { + super.write(b, off, len); + count += len; + progressListener.invoke(count); + } + }; + } + + public int getEstimationSize() throws IOException { + return 0; + } + + public Sender setProgressListener(Callback progressListener) { + this.progressListener = progressListener; + return this; + } } diff --git a/src/org/nutz/http/sender/FilePostSender.java b/src/org/nutz/http/sender/FilePostSender.java index 673051763..97ddd738d 100644 --- a/src/org/nutz/http/sender/FilePostSender.java +++ b/src/org/nutz/http/sender/FilePostSender.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.util.Map; import java.util.Map.Entry; @@ -34,14 +35,14 @@ public static FilePostSender create(Request request) { @Override public Response send() throws HttpException { try { - String boundary = "---------------------------[Nutz]aabbcc" + R.UU32(); + String boundary = "---------------------------[Nutz]" + R.UU32(); openConnection(); setupRequestHeader(); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); setupDoInputOutputFlag(); Map params = request.getParams(); if (null != params && params.size() > 0) { - export(params, conn.getOutputStream(), boundary, request.getEnc()); + export(params, getOutputStream(), boundary, request.getEnc()); } return createResponse(getResponseHeader()); @@ -51,7 +52,7 @@ public Response send() throws HttpException { } } - public static void export(Map params, OutputStream out, String boundary, final String enc) throws IOException { + public static void export(Map params, OutputStream out, final String boundary, final String enc) throws IOException { final DataOutputStream outs = new DataOutputStream(out); for (Entry entry : params.entrySet()) { outs.writeBytes("--" + boundary + SEPARATOR); @@ -63,37 +64,18 @@ public static void export(Map params, OutputStream out, String b @Override public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, LoopException { - File f = null; - if (ele instanceof File) - f = (File) ele; try { - if (f != null && f.exists() && f.length() > 0) { - outs.writeBytes("Content-Disposition: form-data; name=\"" - + key - + "\"; filename=\""); - outs.write(f.getName().getBytes(enc)); - outs.writeBytes("\"" + SEPARATOR); - outs.writeBytes("Content-Type: application/octet-stream" - + SEPARATOR - + SEPARATOR); - InputStream is = null; - try { - is = Streams.fileIn(f); - Streams.write(outs, is); - outs.writeBytes(SEPARATOR); - } - finally { - Streams.safeClose(is); - } - } else { - outs.writeBytes("Content-Disposition: form-data; name=\"" + if (ele != null && ele instanceof File) { + writeFile((File)ele, key, outs, boundary, enc); + return; + } + outs.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"" + SEPARATOR + SEPARATOR); - outs.write(String.valueOf(ele).getBytes(enc)); - outs.writeBytes(SEPARATOR); - } + outs.write(String.valueOf(ele).getBytes(enc)); + outs.writeBytes(SEPARATOR); } catch (Exception e) { throw Lang.wrapThrow(e); @@ -105,4 +87,49 @@ public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueL Streams.safeFlush(outs); Streams.safeClose(outs); } + + protected static void writeFile(File f, String key, DataOutputStream outs, String boundary, final String enc) throws IOException { + outs.writeBytes("Content-Disposition: form-data; name=\"" + + key + + "\"; filename=\""); + outs.write(f.getName().getBytes(enc)); + outs.writeBytes("\"" + SEPARATOR); + outs.writeBytes("Content-Type: application/octet-stream" + + SEPARATOR + + SEPARATOR); + InputStream is = null; + try { + is = Streams.fileIn(f); + Streams.write(outs, is); + outs.writeBytes(SEPARATOR); + } + finally { + Streams.safeClose(is); + } + } + + @Override + public int getEstimationSize() throws IOException { + final int[] count = new int[1]; + for (Entry entry : request.getParams().entrySet()) { + count[0] += 60; + final String key = entry.getKey(); + Object val = entry.getValue(); + if (val == null) + val = ""; + Lang.each(val, new Each() { + public void invoke(int index, Object ele, int length){ + if (ele instanceof File) + count[0]+= ((File)ele).length() + 100; + else + try { + count[0] += (key+ele).getBytes(request.getEnc()).length + 100; + } + catch (UnsupportedEncodingException e) { + } + } + }); + } + return count[0]; + } } \ No newline at end of file diff --git a/src/org/nutz/http/sender/PostSender.java b/src/org/nutz/http/sender/PostSender.java index 7e3c5d99f..28c1e80fe 100644 --- a/src/org/nutz/http/sender/PostSender.java +++ b/src/org/nutz/http/sender/PostSender.java @@ -1,13 +1,16 @@ package org.nutz.http.sender; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import org.nutz.http.HttpException; import org.nutz.http.Request; import org.nutz.http.Response; import org.nutz.http.Sender; +import org.nutz.lang.Lang; import org.nutz.lang.Streams; public class PostSender extends Sender { @@ -29,7 +32,7 @@ public Response send() throws HttpException { conn.addRequestProperty("Content-Length", "" + ins.available()); setupDoInputOutputFlag(); if (null != ins) { - OutputStream ops = Streams.buff(conn.getOutputStream()); + OutputStream ops = Streams.buff(getOutputStream()); Streams.write(ops, ins); Streams.safeClose(ins); Streams.safeFlush(ops); @@ -42,4 +45,20 @@ public Response send() throws HttpException { } } + @Override + public int getEstimationSize() throws IOException { + if (request.getInputStream() != null) { + return request.getInputStream().available(); + } else { + if (null != request.getData()) { + return request.getData().length; + } + try { + return request.getURLEncodedParams().getBytes(request.getEnc()).length; + } + catch (UnsupportedEncodingException e) { + throw Lang.wrapThrow(e); + } + } + } } diff --git a/src/org/nutz/ioc/impl/NutIoc.java b/src/org/nutz/ioc/impl/NutIoc.java index 77ecce6c6..c233bb3da 100644 --- a/src/org/nutz/ioc/impl/NutIoc.java +++ b/src/org/nutz/ioc/impl/NutIoc.java @@ -195,7 +195,7 @@ public T get(Class type, String name, IocContext context) throws IocExcep // 修正对象类型 if (null == iobj.getType()) - if (null == type) + if (null == type && Strings.isBlank(iobj.getFactory())) throw new IocException(name, "NULL TYPE object '%s'", name); else iobj.setType(type); diff --git a/src/org/nutz/lang/Streams.java b/src/org/nutz/lang/Streams.java index eeef000aa..3aa6fad7d 100644 --- a/src/org/nutz/lang/Streams.java +++ b/src/org/nutz/lang/Streams.java @@ -5,6 +5,7 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; @@ -304,9 +305,9 @@ public static String readAndClose(Reader reader) { * @throws IOException */ public static byte[] readBytes(InputStream ins) throws IOException { - byte[] bytes = new byte[ins.available()]; - ins.read(bytes); - return bytes; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(out, ins); + return out.toByteArray(); } /** diff --git a/src/org/nutz/lang/Xmls.java b/src/org/nutz/lang/Xmls.java index 692f7fcab..837bdd66c 100644 --- a/src/org/nutz/lang/Xmls.java +++ b/src/org/nutz/lang/Xmls.java @@ -1,8 +1,11 @@ package org.nutz.lang; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -25,6 +28,7 @@ import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** @@ -43,6 +47,10 @@ public abstract class Xmls { public static DocumentBuilder xmls() throws ParserConfigurationException { return DocumentBuilderFactory.newInstance().newDocumentBuilder(); } + + public static Document xml(InputStream ins) { + return xml(ins, null); + } /** * 快捷的解析 XML 文件的帮助方法,它会主动关闭输入流 @@ -51,9 +59,11 @@ public static DocumentBuilder xmls() throws ParserConfigurationException { * XML 文件输入流 * @return Document 对象 */ - public static Document xml(InputStream ins) { + public static Document xml(InputStream ins, Charset charset) { try { - return xmls().parse(ins); + if (charset == null) + charset = Encoding.CHARSET_UTF8; + return xmls().parse(new InputSource(new InputStreamReader(ins, charset))); } catch (SAXException e) { throw Lang.wrapThrow(e); @@ -69,6 +79,10 @@ public static Document xml(InputStream ins) { } } + public static Document xml(File xmlFile) { + return xml(xmlFile, null); + } + /** * 快捷的解析 XML 文件的帮助方法 * @@ -76,9 +90,11 @@ public static Document xml(InputStream ins) { * XML 文件 * @return Document 对象 */ - public static Document xml(File xmlFile) { + public static Document xml(File xmlFile, Charset charset) { + InputStream ins = null; try { - return xmls().parse(xmlFile); + ins = new FileInputStream(xmlFile); + return xml(ins, charset); } catch (Exception e) { throw Lang.wrapThrow(e); diff --git a/src/org/nutz/lang/born/BorningException.java b/src/org/nutz/lang/born/BorningException.java index c86da9fc4..ac46b7e55 100644 --- a/src/org/nutz/lang/born/BorningException.java +++ b/src/org/nutz/lang/born/BorningException.java @@ -59,6 +59,8 @@ private static String makeMessage(Throwable e, Class type, Object[] args) { } if (null != e) { sb.append(" becasue:\n").append(getExceptionMessage(e)); + } else { + sb.append(" becasue: No suitable Constructor or Factory Method!!"); } return sb.toString(); } diff --git a/src/org/nutz/mvc/impl/ActionInvoker.java b/src/org/nutz/mvc/impl/ActionInvoker.java index 87a673710..87162a079 100644 --- a/src/org/nutz/mvc/impl/ActionInvoker.java +++ b/src/org/nutz/mvc/impl/ActionInvoker.java @@ -71,6 +71,9 @@ public boolean invoke(ActionContext ac) { public ActionChain getActionChain(ActionContext ac) { HttpServletRequest req = ac.getRequest(); String httpMethod = Strings.sNull(req.getMethod(), "GET").toUpperCase(); + if ("POST".equals(httpMethod) && req.getHeader("X-HTTP-Method-Override") != null) { + httpMethod = req.getHeader("X-HTTP-Method-Override").toUpperCase(); + } ActionChain chain = chainMap.get(httpMethod); // 找到了特殊HTTP方法的处理动作链 if (null != chain) { diff --git a/src/org/nutz/mvc/view/RawView.java b/src/org/nutz/mvc/view/RawView.java index 165c6e74a..1b38d7b62 100644 --- a/src/org/nutz/mvc/view/RawView.java +++ b/src/org/nutz/mvc/view/RawView.java @@ -51,6 +51,7 @@ *
  • stream - 表示 application/octet-stream *
  • js - 表示 application/javascript *
  • json - 表示 application/json + *
  • pdf -- 表示application/pdf *
  • jpeg - 表示 image/jpeg 返回值是BufferedImage对象时自动转二进制流,质量为0.8f *
  • jpg - 表示 image/jpeg 返回值是BufferedImage对象时自动转二进制流,质量为0.8f *
  • png - 表示 image/png 返回值是BufferedImage对象时自动转二进制流 diff --git a/src/org/nutz/runner/NutRunner.java b/src/org/nutz/runner/NutRunner.java index 7d21dbf6d..36e56e419 100644 --- a/src/org/nutz/runner/NutRunner.java +++ b/src/org/nutz/runner/NutRunner.java @@ -42,6 +42,11 @@ public abstract class NutRunner implements Runnable { */ protected long interval; + /** + * 报错以后睡眠时间 + */ + protected int sleepAfterError; + /** * 启动于 */ @@ -54,14 +59,29 @@ public abstract class NutRunner implements Runnable { /** * 新建一个启动器 - * @param rname 本启动器的名称 + * + * @param rname + * 本启动器的名称 */ public NutRunner(String rname) { this.rnm = rname; this.count = 0; + this.sleepAfterError = 30; this.lock = new NutLock(); } + /** + * 设置报错以后睡眠时间(单位秒) + * + * @param sec + * 秒 + * @return + */ + public NutRunner setSleepAfterError(int sec) { + this.sleepAfterError = sec; + return this; + } + /** * 主逻辑,用户代码不应该覆盖. */ @@ -85,14 +105,18 @@ public void run() { /** * 注册本对象到线程管理器,已废弃 - * @param me 本对象 + * + * @param me + * 本对象 */ @Deprecated public void reg(NutRunner me) {} /** * 从线程管理器反注册,已废弃 - * @param me 本对象 + * + * @param me + * 本对象 */ @Deprecated public void unreg(NutRunner me) {} @@ -147,7 +171,7 @@ protected void doIt() { catch (Throwable e) { log.warn(String.format("%s has some error", rnm), e); try { - lock.wait(30 * 1000); + lock.wait(sleepAfterError * 1000); } catch (Throwable e1) { log.warn(String.format("%s has some error again", rnm), e); @@ -172,6 +196,7 @@ public String toString() { /** * 是否正在等待运行 + * * @return true,如果正在等待 */ public boolean isWaiting() { @@ -180,6 +205,7 @@ public boolean isWaiting() { /** * 是否正在执行用户代码 + * * @return true,如果正在exec方法内部 */ public boolean isRunning() { @@ -188,6 +214,7 @@ public boolean isRunning() { /** * 获取执行间隔 + * * @return 执行间隔 */ public long getInterval() { @@ -196,6 +223,7 @@ public long getInterval() { /** * 获取最后启动时间 + * * @return 最后启动时间 */ public Date getUpAt() { @@ -204,6 +232,7 @@ public Date getUpAt() { /** * 获取最后一次等待开始的时间 + * * @return 最后一次等待开始的时间 */ public Date getDownAt() { @@ -212,6 +241,7 @@ public Date getDownAt() { /** * 获取本启动器的名称 + * * @return 本启动器的名称 */ public String getName() { @@ -220,6 +250,7 @@ public String getName() { /** * 获取累计的启动次数 + * * @return 总启动次数 */ public int getCount() { @@ -228,6 +259,7 @@ public int getCount() { /** * 获取线程NutLock锁 + * * @return 线程NutLock锁 */ public NutLock getLock() { @@ -236,6 +268,7 @@ public NutLock getLock() { /** * 获取所属线程是否存活 + * * @return 所属线程是否存活 */ public boolean isAlive() { @@ -247,7 +280,9 @@ public boolean isAlive() { /** * 强行关闭所属线程 - * @param err 传给Thread.stop方法的对象 + * + * @param err + * 传给Thread.stop方法的对象 */ @SuppressWarnings("deprecation") public void stop(Throwable err) { diff --git a/test/org/nutz/dao/test/meta/pojo.js b/test/config/dao-test.js similarity index 92% rename from test/org/nutz/dao/test/meta/pojo.js rename to test/config/dao-test.js index 28b6eea91..4c7634d87 100644 --- a/test/org/nutz/dao/test/meta/pojo.js +++ b/test/config/dao-test.js @@ -1,69 +1,72 @@ -var ioc = { -/*------------------------------------------------------------------*/ - - config : { - type : "org.nutz.ioc.impl.PropertiesProxy", - fields : { - paths : ["nutz-test.properties"] - } - }, -// Data source - dataSource : { - type :"org.apache.commons.dbcp.BasicDataSource", - events : { - depose :"close" - }, - fields : { - driverClassName : { - java :"$config.get('driver')" - }, - url : { - java :"$config.get('url')" - }, - username : { - java :"$config.get('username')" - }, - password : { - java :"$config.get('password')" - } - } - }, - - /* - dataSource : { - type : "com.jolbox.bonecp.BoneCPDataSource", - events : { - depose : 'close' - }, - fields : { - driverClass : 'org.h2.Driver', - jdbcUrl : 'jdbc:h2:mem:nutzunit', - username : 'sa', - password : 'sa' - } - },*/ -/*------------------------------------------------------------------*/ -// Dao - dao : { - type :"org.nutz.dao.impl.NutDao", - args : [ {refer :"dataSource"}, {refer :"sqls"} ] - }, -/*------------------------------------------------------------------*/ -// Sqls - sqls : { - type : 'org.nutz.dao.impl.FileSqlManager', - args : ['org/nutz/dao/test/sqls/dir'], - fields : { - regex : '^.*[.]sqls$' - } - }, -/*------------------------------------------------------------------*/ -// Meta service - metas : { - type :"org.nutz.dao.test.meta.Pojos", - args : [ { - refer :"dao" - } ] - } -/*------------------------------------------------------------------*/ +var ioc = { +/*------------------------------------------------------------------*/ + + config : { + type : "org.nutz.ioc.impl.PropertiesProxy", + fields : { + paths : ["nutz-test.properties"] + } + }, +// Data source + dataSource : { + type :"org.apache.commons.dbcp.BasicDataSource", + events : { + depose :"close" + }, + fields : { + driverClassName : { + java :"$config.get('driver')" + }, + url : { + java :"$config.get('url')" + }, + username : { + java :"$config.get('username')" + }, + password : { + java :"$config.get('password')" + } + } + }, + + /* + dataSource : { + type : "com.jolbox.bonecp.BoneCPDataSource", + events : { + depose : 'close' + }, + fields : { + driverClass : 'org.h2.Driver', + jdbcUrl : 'jdbc:h2:mem:nutzunit', + username : 'sa', + password : 'sa' + } + },*/ +/*------------------------------------------------------------------*/ +// Dao + dao : { + type :"org.nutz.dao.impl.NutDao", + args : [ {refer :"dataSource"}, {refer :"sqls"} ], + fields : { + interceptors : ["log", "time"] + } + }, +/*------------------------------------------------------------------*/ +// Sqls + sqls : { + type : 'org.nutz.dao.impl.FileSqlManager', + args : ['org/nutz/dao/test/sqls/dir'], + fields : { + regex : '^.*[.]sqls$' + } + }, +/*------------------------------------------------------------------*/ +// Meta service + metas : { + type :"org.nutz.dao.test.meta.Pojos", + args : [ { + refer :"dao" + } ] + } +/*------------------------------------------------------------------*/ } \ No newline at end of file diff --git a/test/org/nutz/dao/test/DaoCase.java b/test/org/nutz/dao/test/DaoCase.java index 96780bf53..5f546cedb 100644 --- a/test/org/nutz/dao/test/DaoCase.java +++ b/test/org/nutz/dao/test/DaoCase.java @@ -17,7 +17,7 @@ public abstract class DaoCase { @Before public void setUp() { Trans.DEBUG = false; - ioc = Nutzs.getIoc("org/nutz/dao/test/meta/pojo.js"); + ioc = Nutzs.getIoc("config/dao-test.js"); dao = ioc.get(Dao.class, "dao"); pojos = ioc.get(Pojos.class, "metas"); before();