星期三, 十月 11, 2006

HTML Comments

HTML中的注释,定义很简单,不过却比较搞。因为末尾的"--"和">"之间允许有空格,而开头的"<"和"--"之间却不可以。这就暗示了,注释的内容中,如果出现连续的"-",那么,解析将有可能出错

Firefox表叫严格地遵循W3C标准,所以,碰到这种情形(html注释内容中包含"--"字样),有时会将部分注释显示出来。不过,IE不会,似乎IE对这些不符合规范的格式兼容得很好


星期六, 九月 23, 2006

HTML, XML & XHTML

Understanding HTML, XML and XHTML

如果你现在正在尝试设计符合XHTML标准的HTML网页,那么,这篇文章值得一看。

可能已经碰到的问题:

  • 为什么<script>标签必须显式地以</script>闭合(<script src="blah..." type="text/javascript" />这种self-closed形式的语句不能工作)
  • 为什么<img src="blah..." />可以正常显示?

答案很简单,现在所有浏览器的HTML Parser实际上是不支持“自闭”格式的tag的。象<img src="blah..." />这样格式的tag描述语句能够工作,其实是html parser误以为"/"是此标签的一个非法的attribution。对于非法的attribution的处理的不同,就造成了以上两个不同的结果。


XMLHttpRequest对象实现的安全性考虑:为什么不能跨域访问

AJAX通过XMLHttpRequest对象发送http请求,但是只能访问本站点内的资源,站点A的页面通过此对象访问站点B上的资源的行为是被禁止的。

原因,参考:http://www.w3.org/TR/XMLHttpRequest/#security

The restrictions are there to stop untrusted users of the API from using the implementation to retrieve sensitive data. Specifically, an implementation in a web browser will often want to restrict pages from a website A to retrieve data from website B. The reason for this is that website B could reside inside a corporate firewall. If data could be retrieved from website B then website A could use the browser effectively circumvent the firewall.

假设站点B是一个公司的内网(在防火墙后面),如果允许站点A的某个网页上的XMLHttpRequest对象访问站点B的资源的话,那么,当站点B所在公司的员工访问此页面时,站点A就可以通过脚本获得内网资源,使得防火墙形同虚设。

如果站点A和站点B是一个域名下的两个站点(子域),Abe Fettig’s Weblog介绍了一个方法(iframe+ajax)让XMLHttpRequest对象跨站点访问:How to make XmlHttpRequest calls to another server in your domain


星期六, 七月 22, 2006

javascript中的变量声明和赋值与否

如果变量a未声明,对变量a的调用会触发浏览器js engin报错:对象不存在

  • 判别变量是否声明的操作:typeof(a) == "undefined";
  • 如果变量a已被声明但尚未被初始化(赋值),那么此变量的值为undefined:

var a;

a == undefined; //return true

  • 可以使用eval来动态创建变量:

eval("var a");


星期三, 四月 12, 2006

javascript测试工具JsUnit

测试浏览器端页面上的javascript。方法类似测试Java class的JUnit。他们都是XUnit的一部分

see http://www.edwardh.com/jsunit/


XUnit包含的单元测试包除了JUnit(java), JsUnit(js), 还有CppUnit(C++), PHPUnit(php), UTPlsql(oracle PL/SQL)等。

What is an XUnit framework?

  • A standardized set of language-independent concepts and constructs for writing and running unit tests
  • There is an XUnit framework for virtually every language
  • Traditionally open-source
  • All support certain constructs:
  • TestCase and TestSuite are well-defined entities
  • The assertion syntax follows rough standards, e.g. assertEquals(comment, expected, actual)
  • All provide a TestRunner program to run tests

Listed at http://xprogramming.com/software.htm


星期六, 四月 01, 2006

Ruby在线交互教程

在线教程海了去,不过以这种交互方式进行的实在新鲜,a hands-on tutorial。用了一下,确实方便直观。

try ruby!(in your browser)

very hot! In delicious,this url has been saved by 3389 people


星期日, 三月 12, 2006

正确使用链接模拟浏览器的Go Back功能

正确方式(e.g.):

<a href="back_url.html" onClick="javascript:history.back();return false;">返回</a>


1)这种写法(<a href="javascript:history.back();">返回</a>)为什么不对?
如果浏览器不支持js,那么,浏览器会去直接要求打开href指向的页面


2)onClick中,如果忽略了"return false;"会如何?
return false,在这里,是告诉浏览器,取消加载href指向的url;如果在onClick内,忽略了return false();那么,浏览器将在history.back()之后,会再次要求打开href指向的页面,结果造成页面的重新加载,在某些情形下,会导致页面出错


星期一, 二月 20, 2006

再谈tomcat 5.x环境下的字符编码问题

@see "Tomcat环境下,字符编码的filter"

确实比较晕,tomcat server让多字节编码的国家吃尽苦头

原先以为,加这么个filter就一了百了了,实际上要复杂的多

原因是,这个filter的方法对tomcat 4.x版本是起作用的,但是在tomcat 5以后,就不完全适合了

为什么呢?

因为在http method是GET的时候,tomcat server 5.x会根据server.xml配置中<Connector>元素的URIEncoding属性设置,自己处理字符编码(此处讨论,不考虑主要是为了和tomcat 4兼容的useBodyEncodingForURI属性)

这也就意味着,如果你使用tomcat 5.x,你必须在doFilter的时候,区分http请求的方法是GET还是POST,如果是POST,调用request.setCharacterEncoding(encoding)来处理字符编码;如果是GET,则应当将这转换编码的工作交给tomcat server。为了让tomcat能正常处理你的字符编码,必须注意要设置正确的URIEncoding属性。譬如,网页编码是GBK的,则应当设置URIEncoding="GBK",特别的,如果需要生成URL的,则URL中的中文字符必须转码成符合GBK编码的%xx的格式。

对于使用GBK编码,使用tomcat 5.x server的网站,如下的配置和设计是正确的:
1)<Connector ... URIEncoding="GBK" ... />
2)使用filter,encoding=GBK
3)filter中区分http请求,代码如下:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {

// Conditionally select and set the character encoding to be used
if (ignore || (request.getCharacterEncoding() == null)) {
String encoding = selectEncoding(request);
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (encoding != null && httpServletRequest.getMethod().equalsIgnoreCase("POST"))
request.setCharacterEncoding(encoding);
}

// Pass control on to the next filter
chain.doFilter(request, response);

}
4)直接使用URL的,如果URL中的参数值包含中文,则必须根据GBK的编码方式将每个中文字符转成%xx%xx的格式,如:<A href="/search.shtml?action=search&key=%CC%AB%D1%F4%C4%DC">太阳能</A>

星期四, 一月 12, 2006

drw, tomcat and xalan exception

访问dwr的动态js时,抛出异常:

- StandardWrapper.Throwable
javax.xml.transform.TransformerFactoryConfigurationError: Provider org.apache.xalan.processor.TransformerFactoryImpl not found
at javax.xml.transform.TransformerFactory.newInstance(Unknown Source)

本地测试是好的,提交到服务器上,报错。

在lib下增加xalan.jar包,正常了

或者:http://www.robsanheim.com/2005/07/24/dwr-tomcat-55-and-xalan-classpath-error/

查看$catalina_home/common/endorse目录,果然发现xml-apis.jar文件。删除,同时删除新增的xalan.jar包,重启tomcat,正常了。就是这个endorse/xml-apis.jar和项目内的lib/xml-apis.jar冲突了,导致这个异常

 查看全文

星期三, 一月 04, 2006

Hibernate Lucene Integration

http://www.hibernate.org/hib_docs/annotations/reference/en/html/lucene.html

Chapter 4. Hibernate Lucene Integration
Lucene is a high-performance Java search engine library available from the Apache Software Foundation. Hibernate Annotations includes a package of annotations that allows you to mark any domain model object as indexable and have Hibernate maintain a Lucene index of any instances persisted via Hibernate.

4.1. Using Lucene to index your entities
4.1.1. Annotating your domain model
First, we must declare a persistent class as @Indexed:

@Entity
@Indexed(index="indexes/essays")
public class Essay {
...
}
The index attribute tells Hibernate where the Lucene index is located (a directory on your file system).

Lucene indexes contain four kinds of fields: keyword fields, text fields, unstored fields and unindexed fields. Hibernate Annotations provides annotations to mark a property of an entity as one of the first three kinds of indexed fields.

@Entity
@Indexed(index="indexes/essays")
public class Essay {
...

@Id
@Keyword(id=true)
public Long getId() { return id; }

@Text(name="Abstract")
public String getSummary() { return summary; }

@Lob
@Unstored
public String getText() { return text; }

}
These annotations define an index with three fields: Id, Abstract and Text.

Note: you must specify @Keyword(id=true) on the identifier property of your entity class.

4.1.2. Enabling automatic indexing
Finally, we enable the LuceneEventListener for the three Hibernate events that occur after changes are committed to the database.

<hibernate-configuration>
...
<event type="post-commit-update"
<listener
class="org.hibernate.lucene.event.LuceneEventListener"/>
</event>
<event type="post-commit-insert"
<listener
class="org.hibernate.lucene.event.LuceneEventListener"/>
</event>
<event type="post-commit-delete"
<listener
class="org.hibernate.lucene.event.LuceneEventListener"/>
</event>
</hibernate-configuration>


基于apache Lucene的mp3搜索器

辗转摘抄,出处不明

前些日子找机器上的一首老歌时,费了些周折,后想到既然这些mp3有自己的标签信息,为何不利用起来呢?笔者就尝试用Lucene实现,分两部分,Mp3Indexer.java是创建索引的,mp3search.jsp是搜索mp3的页面。
下面是Mp3Indexer.java的代码。
package mp3indexer;
import java.io.*;
import java.text.*;
import java.util.*;

import org.apache.lucene.analysis.cjk.*;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;

public class Mp3Indexer
{
public final static String mp3Path="d:mp3";//mp3所在目录
public final static String indexPath="c:mp3Indexer";//索引存放目录
public static void main(String[] args) throws ClassNotFoundException, IOException{
try {
IndexWriter writer = new IndexWriter(indexPath, new CJKAnalyzer(), true);
indexMp3s(writer, new File(mp3Path));

System.out.println("优化中....");
writer.optimize();
writer.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

public static void indexMp3s(IndexWriter writer, File file) throws Exception {
if (file.isDirectory()) {
String[] files = file.list();
for (int i = 0; i < files.length; i++) {
indexMp3s(writer, new File(file, files[i]));
}
}
else if (file.getPath().endsWith(".mp3")) { //只对 MP3 文件做索引
System.out.print("正在处理文件:" + file + " ....");
// Add mp3 file ....
Document doc = new Document();
doc.add(Field.Text("name", file.getName())); //索引文件名
doc.add(Field.UnIndexed("modified", DateFormat.getDateTimeInstance().format(new Date(file.lastModified())))); //索引最后修改时间
doc.add(Field.Text("size",""+NumberFormat.getNumberInstance().format(file.length()/1048576.0)+"MB")); //索引最后修改时间

FileReader fReader = new FileReader(file);
java.io.RandomAccessFile r=new RandomAccessFile(file,"r");
r.seek(file.length()-128);
byte[] bt=new byte[127];
r.read(bt);
String labelInfo=new String(bt,"GB2312");
System.out.println(labelInfo);

if (labelInfo.startsWith("TAG")) {
doc.add(Field.Text("comment", labelInfo));
}
System.out.println("[处理完成]");

r.close();
fReader.close();
writer.addDocument(doc);
} //end else if
}

} //end class


为blog增加全文搜索-Lucene

辗转摘抄,来处不明

全文搜索能大大方便用户快速找到他们希望的文章,为blog增加一个全文搜索功能是非常必要的。然而,全文搜索不等于SQL的LIKE语句,因为关系数据库的设计并不是为全文搜索设计的,数据库索引对全文搜索无效,在一个几百万条记录中检索LIKE '%A%'可能会耗时几分钟,这是不可接受的。幸运的是,我们能使用免费并且开源的纯Java实现的Lucene全文搜索引擎,Lucene可以非常容易地集成到我们的blog中。

Lucene不提供直接对文件,数据库的索引,只提供一个高性能的引擎,但接口却出人意料地简单。我们只需要关心以下几个简单的接口:

  • Document:代表Lucene数据库的一条记录,也代表搜索的一条结果。
  • Field:一个Document包含一个或多个Field,类似关系数据库的字段。
  • IndexWriter:用于创建新的索引,也就是向数据库添加新的可搜索的大段字符串。
  • Analyzer:将字符串拆分成单词(Token),不同的文本对应不同的Analyzer,如HtmlAnalyzer,PDFAnalyzer。
  • Query:封装一个查询,用于解析用户输入。例如,将“bea blog”解析为“同时包含bea和blog的文章”。
  • Searcher:搜索一个Query,结果将以Hits返回。
  • Hits:封装一个搜索结果,包含Document集合,能非常容易地输出结果。

下一步,我们需要为Article表的content字段建立全文索引。

首先为Lucene新建一个数据库,请注意这个数据库是Lucene专用的,我们不能也不必知道它的内部结构。Lucene的每个数据库对应一个目录,只需要指定目录即可:
String indexDir = "C:/search/blog";
IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), true);
indexWriter.close();

然后添加文章,让Lucene对其索引:
String title = "文章标题" // 从数据库读取 String content = "文章内容" // 从数据库读取

// 打开索引:
IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false); // 添加一个新记录: Document doc = new Document(); doc.add(Field.Keyword("title", title)); doc.add(Field.Text("content", content));

// 建立索引:
indexWriter.addDocument(doc); // 关闭: indexWriter.close();

要搜索文章非常简单:
Searcher searcher = new IndexSearcher(dir);
Query query = QueryParser.parse(keyword, "content", new StandardAnalyzer());
Hits hits = searcher.search(query);
if(hits != null){for(int i = 0;i < hits.length(); i++){ Document doc = hits.doc(i); System.out.println("found in " + doc.get("title")); System.out.println(doc.get("content")); } }
searcher.close();

我们设计一个LuceneSearcher类封装全文搜索功能,由于必须锁定数据库所在目录,我们把数据库设定在/WEB-INF/search/下,确保用户不能访问,并且在配置文件中初始化目录:

<bean id="luceneSearcher" class="org.crystalblog.search.LuceneSearcher"> <property name="directory"> <value>/WEB-INF/search/</value>
</property>
</bean>


星期三, 十二月 21, 2005

Hibernate,left join fetch,重复对象(记录)的问题

Hibernate 3.0中,mapping文件缺省配置的one-to-many的子集合是lazy declarations的,fetch的模式是join,当应用需要访问子表(1:M)中的数据时,hibernate框架会主动发起一条查询的sql。当我们需要在获取主记录的同时得到子记录的数据时,我们可以使用HSQL,“left join fetch”。但是当子记录存在时,主对象记录会有重复。

解决方法:http://forum.hibernate.org/viewtopic.php?t=938705&highlight=onetomany+query

fetch return distinct root instances

join has the same meaning as SQL join, so just use
Set results = new HashSet(query.list()) if you don't want duplicate results.


星期二, 十二月 13, 2005

FileUpload, session还是request

使用struts.upload模块处理文件上传的时候,会用到org.apache.commons.fileupload包。

如果包含文件上传的Action的scope="session",那么当前会话下,临时的上传文件始终有效,除非主动删除它。此时,重新启动Tomcat,在load org.apache.commons.fileupload.DefferredFileOutputStream时,系统会抛掷异常:

java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: org.apache.commons
.fileupload.DeferredFileOutputStream

常理来看,没必要创建会话级的上传文件的Formbean,因为太占内存了。但是Struts配置中,缺省的scope是session,所以要特别注意。


星期六, 十二月 10, 2005

java.lang.NoClassDefFoundError: javax/activation/DataSource

奇怪的异常。在创建一个MimeMessage对象的时候抛出的。

在项目的WEB-INF/lib中的确有activation.jar,但却总报这个错误。直到后来将activation.jar包拷贝到tomcat的common/lib下才算解决。不过解决的不明不白,郁闷。