XML创建/解析/验证


最近用到XML结合JMS TextMessage传输数据的技术。采用TextMessage+XML格式传输对象是考虑到这个平台不仅仅使用Java语言开发,同时还需要能够支持其他语言接入:比如C, Ruby, Perl, Python, PHP, ActionScript/Flash, Smalltalk,借助于ActiveMQ提供的Stomp,这些非Java语言能够很方便的就接入平台。

总结下来整个传输过程如下(Java to Java):

DTO对象->XML对象->XML文本->JMS TextMessage->XML文本->XML对象->DTO

对象

涉及技术点:

  1. JavaDTO对象如何转变为XML对象
  2. 如何从XML对象中获得XML文本
  3. 如何通过XML文本构造XML对象
  4. 遍历XML对象创建JavaDTO对象

先说第一点,JavaDTO对象如何转变为XML对象

// 创建DocumentBuilderFactory,创建和解析XML都需要
DocumentBuilderFactory domfac = DocumentBuilderFactory.newInstance();
// 创建DocumentBuilder
DocumentBuilder builder = domfac.newDocumentBuilder();
// 新建文档
Document doc = builder.newDocument();
doc.setXmlVersion("1.0");
// 创建文档根节点
Element root = doc.createElement("root");
// 循环创建叶子节点
Element leaf = doc.createElement("leaf");
// 添加叶子节点属性
leaf.setAttribute("name", "从Java对象中获取");
// 增加叶子节点至根节点
root.appendChild(leaf);

至此,XML对象已经创建完成,现在需要从XML对象中获取XML文本。

Continue reading “XML创建/解析/验证”

Advertisements

FWK005 parse may not be called while parsing.


最近在使用javax.xml.parsers.DocumentBuilder解析xml文件的时候偶尔会出错:

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:263)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:284)
        at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:208)
...

跟踪了一下代码,发现这个异常是在com.sun.org.apache.xerces.internal.parsers.DTDConfiguration.parse(DTDConfiguration.java:546)抛出来的。该段代码结构如下:

if(fParseInProgress) {
    throw new XNIException("FWK005 parse may not be called while parsing.");
}

fParseInProgress = true;

// 解析xml文件

finally {
    fParseInProgress = false;
}

从程序逻辑来看,如果当前DocumentBuilder对象正在转换文档,此时再次请求转换文档,那么直接抛出XNIException(“FWK005 parse may not be called while parsing.”);异常。

这个问题也比较好解决,一种是对转换xml文档的方法,增加synchronized关键字,这样子不会有两个线程同时访问方法。

还有一种方法是创建一个DocumentBuilder类型的ThreadLocal变量,这样子每个线程都拥有自己的DocumentBuilder对象,能够同时转换多个xml文件。代码如下:

private static ThreadLocal docBuildeIns = new ThreadLocal() {
    protected DocumentBuilder initialValue() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            String msg = "DocumentBuilder 对象初始化失败!";
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
    }
};

解析xml文件时的调用方法:

docBuildIns.get().parse(File);

get()方法返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。

Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.


今天碰到了一个很头疼的问题,自定义的一个XML文档,在eclipse中做测试的时候能够正常解析,但是一旦部署到服务器上就会出现下面的错误:

[Fatal Error] :1:1476: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.
net.sf.json.JSONException: nu.xom.ParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”. at line 1, column 1476
at net.sf.json.xml.XMLSerializer.read(XMLSerializer.java:331)
at net.sf.json.xml.XMLSerializer.FromStream(XMLSerializer.java:391)
at net.sf.json.xml.XMLSerializer.FromFile(XMLSerializer.java:355)
……
Caused by: nu.xom.ParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”. at line 1, column 1476
at nu.xom.Builder.build(Unknown Source)
at nu.xom.Builder.build(Unknown Source)
at net.sf.json.xml.XMLSerializer.read(XMLSerializer.java:309)
… 62 more
Caused by: org.xml.sax.SAXParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.

从错误提示来看,是有标签没有结束,清空文件内容,一点点的添加,逐步排查,发现所有的标签都正常结束了阿。按照异常提示,一层层阅读源代码,从json-lib.jar到xom.jar,最后恍然大悟,应该直接拿

org.xml.sax.SAXParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.

作为搜索条件,果不其然,使用这个关键字搜索的结果比net.sf.json.JSONException和nu.xom.ParsingException的结果多多了。

最后在关于dom4j解析编码的问题,org.xml.sax.SAXParseException: Invalid byte 1 of 1-byte UTF-8 sequence找到了关联答案,在二楼有人说

UTF-8编码中中文解析有问题
将编码格式改成“GB2312”后就可以正常解析了。<?xml version=”1.0″ encoding=”GB2312″?>

马上联想,这么奇怪的问题是不是也是编码问题导致的呢?最后把encoding从UTF-8修改为GB18030,问题解决。

疑问:为什么在eclipse中测试的时候没有出现问题,部署到tomcat或者weblogic都会出现这样的问题呢?

问题的根本原因找到了: Continue reading “Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.”

Spring MVC 如何防止XSS、SQL注入攻击


在Web项目中,通常需要处理XSS,SQL注入攻击,解决这个问题有两个思路:

  • 在数据进入数据库之前对非法字符进行转义,在更新和显示的时候将非法字符还原
  • 在显示的时候对非法字符进行转义

如果项目还处在起步阶段,建议使用第二种,直接使用jstl的<c:out>标签即可解决非法字符的问题。当然,对于Javascript还需要自己处理一下,写一个方法,在解析从服务器端获取的数据时执行以下escapeHTML()即可。

附:Javascript方法:

String.prototype.escapeHTML = function () {
return this.replace(/&/g, ‘&amp;’).replace(/>/g, ‘&gt;’).replace(/</g, ‘&lt;’).replace(/”/g, ‘&quot;’);
}

如果项目已经开发完成了,又不想大批量改动页面的话,可以采用第一种方法,此时需要借助Spring MVC的@InitBinder以及org.apache.commons.lang.PropertyEditorSupport、org.apache.commons.lang.StringEscapeUtils

public class StringEscapeEditor extends PropertyEditorSupport {
private boolean escapeHTML;
private boolean escapeJavaScript;
private boolean escapeSQL;

public StringEscapeEditor() { super(); }
public StringEscapeEditor(boolean escapeHTML, boolean escapeJavaScript, boolean escapeSQL) {
super();
this.escapeHTML = escapeHTML;
this.escapeJavaScript = escapeJavaScript;
this.escapeSQL = escapeSQL;
}

@Override
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
String value = text;
if (escapeHTML) {   value = StringEscapeUtils.escapeHtml(value);     }
if (escapeJavaScript) {     value = StringEscapeUtils.escapeJavaScript(value);     }
if (escapeSQL) {     value = StringEscapeUtils.escapeSql(value);     }     setValue(value);     }
}

@Override
public String getAsText() {     Object value = getValue();     return value != null ? value.toString() : “”;    }
}

在使用StringEscapeUtils时需要注意escapeHtml和escapeJavascript方法会把中文字符转换成Unicode编码,如果通过<c:out>标签或者EL表达式展示时,能够正确还原,但是如果使用类似于Ext这样的前端组件来展示这部分内容时,不能正常还原,这也是我为什么放弃了第一种方法,直接使用第二种方法的原因。

在上面我们做了一个EscapeEditor,下面还要将这个Editor和Spring的Controller绑定,使服务器端接收到数据之后能够自动转移特殊字符。
下面我们在@Controller中注册@InitBinder

@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringEscapeEditor(false, false, false));
}

这个方法可以直接放到abstract Controller类中,这样子每个Controller实例都能够拥有该方法。至此第二种方法完成,但是在还原的方法暂时还没有。O(∩_∩)O…

Spring+BlazeDS+Flex+SpringSecurity配置


最近一个项目用到了Spring +BlazeDS+Flex+SpringSecurity的配置,在这里记录一下此种框架的配置方法,仅作备忘。

web.xml

<!– 初始化spring关联配置,比如SpringSecurity, Hibernate等等 –>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/*.xml
</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!– 日志服务监听器 –>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!– flexhttp请求监听器 –>
<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>
<!– SpringSecurity HTTP访问过滤器,实现HTTP请求拦截 –>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!– 编码过滤器,经过测试,flex在进行数据传输的时候,不需要此过滤器也能正确传输中文,此处主要是为了非flex类型的请求配置 –>
<filter>
<filter-name>encoding-filter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!– SpringMVC REST服务需要,因为浏览器的HTTP不支持PUT,DELETE METHOD,通过这个过滤器可以使用POST伪装PUT,DELETE METHOD实现 –>
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!– Hibenate 过滤器,保证一个HTTP请求使用同一个Session,并且提供了flushMode,使程序员在开发的时候不必关心缓存刷新的问题 –>
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param> Continue reading “Spring+BlazeDS+Flex+SpringSecurity配置”

如何正确释放Hibernate Session使用的资源


最近一个项目使用的SpringMVC+SpringSecurity+Hibernate的开发框架,由于需要使用到多个视图表现方式,所以在Web.xml中针对不同的视图表现方式配置了不同的初始化入口,这直接导致一个项目中存在多个连接缓冲池,其中SpringSecurity单独使用一个连接缓冲池,业务代码单独使用一个,这就引出来下面的问题。

在业务代码中,由于在Service层进行了事务控制,所以业务代码的DAO继承HibernateDaoSupport之后,直接使用getSession().createSQLQuery的方式操作数据库,不用自己管理session。

而在SpirngSecurity中,由于自己实现了UserDetailsService接口,在自定义的UserDetailsService中查询了数据库,获得用户信息。在做压力测试的时候发现很容易就出现can’t open connection的问题,并且在停止压力测试之后,已经打开的链接并不不会被关闭。通过检查代码发现,在UserDetailsService中并没有进行事务控制,也没有对Session进行释放资源的操作,问题就出在这里了。

由于业务代码使用了事务控制,在事务结束的时候框架会释放Session关联的资源,而UserDetailsService中没有事务控制,所以我们需要手动进行Session相关资源的释放。将getSession().createSQLQuery修改为Session sess = getSession(); sess.createSQLQuery,查询完数据之后执行super.releaseSession(sess);释放Session资源。这样子操作之后,没有再出现无法打开连接的问题,并且在Connection空闲指定时间之后,自动关闭了。

进一步阅读super.releaseSession()方法的代码发现,在HibernateDaoSupport中是通过SeesionFacotryUtils.releaseSession(Session, SessionFactory)进行Session资源释放的,在这个方法中,对Session进行了是否存在事务的判断,如果Session本身仍然在事务内,则不关闭。想必,如果业务层存在事务控制,同时在DAO进行releaseSession的操作时,Session并不会真正的被关闭,而要等到事务结束的时候由事务进行关闭操作了。

从Jabberd2迁移到Openfire(一)


最近,把在公司运行了一年多的XMPP聊天服务器从Jabberd2迁移到了Openfire,为什么做这样的迁移,基于以下几点考虑:

  1. Jabberd2没有图形界面的后台管理程序,添加删除用户时,需要SSH登录到Linux服务器,然后命令行操作Mysql,对管理人员的要求比较高。
    Openfire基于Web方式后台管理,简单明了,不熟悉Linux和Mysql的人员也能迅速上手,并且服务器端能够控制用户组……。
  2. 在Jabberd2下面,一直没能实现帐号的密码加密,聊天帐号的密码都是明文存储在authreg表中,这样不是非常安全。
    Openfire密码默认加密存储,并且通过鼠标简单的点击几下就能启用SSL。
  3. Jabberd2未实现XEP-0096: SI File Transfer协议支持,并且开发缓慢,而在应用的环境中,存在服务器支持文件传输功能的需求(实现跨网段文件传送)
    Openfire实现了XEP-0096: SI File Transfer协议,配合使用Spark时,可以发送文件,并且截屏发送。
  4. 通过插件的支持Openfire还能支持视频,语音,以及和普通电话语音等等。

Openfire已知问题

  1. 如果需要能够支持中文、日文等,除了在连接Mysql的JDBC驱动之后增加
    ?useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8
    还需要在创建数据库的时候指定数据库的默认字符编码集为UTF8
    mysql> Create database openfire default character set UTF8;

下面说一下迁移的思路。
从Jabberd2迁移到Openfire主要是把用户基本细心及用户的联系人列表这些数据移到新的环境。Jabberd2使用Mysql保存了这些数据,Openfire也可以选择使用Mysql保存用户的数据。

  1. 首先在Openfire安装一个User Import Export插件,这个插件支持用户数据以XML格式导入导出。
  2. 把Jabberd2中的数据做成符合User Import Export插件要求的XML格式,这块自己写了一个Java小工具,下面主要讲讲这个小工具的实现思路。

需要导出的用户基本信息有用户名,密码,昵称,昵称这块,由于设置不规范,分散在nickname,n_family,n_given,n_middle四个字段中。
用户列表信息有所有者,jid,用户自定义的name,所属组名

User Import Export要求的XML格式如下:

<Openfire>
<User>
<Username>leo</Username>
<Password>leopwd</Password>
<Email>shaohu.xie@gmail.com</Email>
<Name>Xie Shaohu</Name>
<CreationDate>1252398257423</CreationDate>
<ModifiedDate>0</ModifiedDate>
<Roster>
<Item jid=”wendy@im.uniqueme.cn” askstatus=”-1″ recvstatus=”-1″ substatus=”3″ name=”Wendy”>
<Group>Family</Group>
<Group>Other</Group>
</Item>
<Item jid=”bob@im.uniqueme.cn” askstatus=”-1″ recvstatus=”-1″ substatus=”3″ name=”Bob”>
<Group>Other</Group>
</Item>
</Roster>
</User>
</Openfire>

从上面的XML格式可以看出每个<User>标签下面都包含了用户的用户名、密码、用户列表等信息。每个用户的用户列表下标明了列表中的每个用户的jid以及昵称,及所属组。