Tomcat启动地址解析错误

贴log:
SEVERE: Protocol handler pause failed
java.net.UnknownHostException: NEOSTA: NEOSTA
at java.net.InetAddress.getLocalHost(InetAddress.java:1474)
at org.apache.jk.common.ChannelSocket.unLockSocket(ChannelSocket.java:484)
at org.apache.jk.common.ChannelSocket.pause(ChannelSocket.java:283)
at org.apache.jk.server.JkMain.pause(JkMain.java:681)
at org.apache.jk.server.JkCoyoteHandler.pause(JkCoyoteHandler.java:153)
at org.apache.catalina.connector.Connector.pause(Connector.java:1073)
at org.apache.catalina.core.StandardService.stop(StandardService.java:563)
at org.apache.catalina.core.StandardServer.stop(StandardServer.java:744)
at org.apache.catalina.startup.Catalina.stop(Catalina.java:628)
at org.apache.catalina.startup.Catalina$CatalinaShutdownHook.run(Catalina.java:671)
关键词:UnknownHostException, JK
原因在google的第一个,虽然上面说的是AIX,但也适用于普通Linux:RHEL5原装的GCJ太山寨了,记得在启动tomcat前要指定JAVA_HOME。

Adding HTTP response header in j_security_check

In WebSphere, the login stuff is handled by the /j_security_check servlet if configured to use federated repository, which is at the top of the WAS invocation chain and there’s on way to intercept the invocation in web.xml. But the login module (which is based on JAAS, Java Authentication & Authorization Service) itself provides a full mechanism of interception and chaining. Besides the open standard JAAS, IBM also develops an proprietary extenstion mechanism named TAI(Trust Association Interceptor). Advanced authentication in WebSphere Application Serveris a good article about this along with a full example. And it’s the main source of this wiki page. It is strongly recommended to read the above link if you want to find out more. Another good reference is the IBM redbook “Websphere Application Server V6.1 Security Handbook”.
WebSphere defines 4 system login configuration that are used in specific siturations related to security:

  • WEB_INBOUND
  • RMI_INBOUND
  • RMI_OUTBOUND
  • DEFAULT

The picture above shows a simplified view of the authentication flow for the WEB_INBOUND configuration:
Web_INBOUND flow
So our custom login module can be placed at the bottom of the chain.
The custom login module can use callbacks to obtain information relevant to the authentication from the envirnoment. In our case, we need to obtain an HttpServletResponse instance in order to populate the P3P header attirbute. There are several callbacks that are available for WEB_INBOUND:

  • NameCallback
  • PasswordCallback
  • WSCredTokenCallbackImpl
  • WSTokenHolderCallback
  • WSServletRequestCallback
  • WSServletResponseCallback
  • WSAppContextCallback

There’s an demostrative example of populating P3P headers in the attachments. There are a few tips while developing and deploying the module:

  • The java version of the byte code should be compliant with WebSphere runtime environment
  • The WebSphere jar file which contains the WSServletResponseCallback lies in WS_ROOT/runtimes/com.ibm.ws.admin.client_6.1.0.jar
  • The output class file should be packed in a jar file and placed in WS_ROOT/lib/ext for WebSphere can reach it globally

Deploy

  1. A similar procedure is also presented in http://www.ibm.com/developerworks/websphere/techjournal/0508_benantar/0508_benantar.html with snapshots.
  2. Open your WebSphere Administrative Console -> Security -> Secure administration, applications, and infrastructure
  3. In the “Authentication” fieldset on the right, follow “Java Authentication Authorization Service” -> “System Logins” -> “WEB_INBOUND” -> “JAAS Login Modules”
  4. Click on “New” button, then input the classname of your custom login module. In this case is “demo.P3PAfterLoginModule”. Check “Use login module proxy”. Save the custom module.
  5. You can adjust the order of the execution of your custom modules. Since our module is intended for the last one, it is not necessary to do this step.
  6. Flush your modification to the configuration file and restart WebSphere.

Java的几个术语(覆写, 重载, 隐藏, 遮蔽, 遮掩)

这几天看Java Puzzler,被里面的题目折腾得死去活来,特别是标题里的几个概念。于是决定发篇小文澄清下几个概念。
覆写(Override)
即子类的实例方法覆写了父类的实例方法,即函数的多态。
陷阱1:覆写仅仅对于实例方法有效,对于属性和静态方法不适用。后两者的情况属于隐藏(hide)的范畴。覆写的函数调用时动态绑定的,根据实际类型进行调用;而隐藏的调用则是静态绑定,编译期间就确定了。
陷阱2:覆写要求父类函数的参数和子类函数的参数类型相同。举两个例子:

        public class Base{
                public void foo(Object a){}
                public void bar(int a){}
        }
        public class Derived extends Base{
                @Override
                public void foo(String a){}
                @Override
                public void bar(Integer a){}
        }

上面的代码能通过编译吗?结论是都不行。Java要求类型严格的一致,所以上面的例子实际上表现的是函数的重载。
正如例子中写的那样,对于Java5以上的版本,确定子类函数是否覆写了父类函数,只需要给子类函数添加一个@Override注释,如果子类函数并未覆写任何父类函数,则无法通过编译。
陷阱3:覆写要求父类函数的访问限制对子类可见(public/protected/package)。上例子:

        public class Base{
                private void foo(){System.out.println("base foo");}
                public void bar(){foo();}
        }
        public class Derived extends Base{
                public void foo(){System.out.println("derived foo");}
                public void static main(String[] args){new Derived().bar();}
        }

上面的例子将会输出base foo。子类并没有覆写父类的函数。
在Java虚拟机中,多态的调用通过invokevirtual和invokeinterface两条指令进行调用,前者用于类引用,后者用于接口引用。上面两个方法均是动态绑定。而invokestatic和invokespecial指令使用静态绑定。前者用于静态方法的调用,后者用于实例方法的调用,分为三种情况:第一种用于构造函数(<init>),显然构造函数不需要动态绑定;第二种情况用于调用私有的实例方法,也是上面这个例子里的情况,原因在于,既然这个方法不可能被子类方法覆写,所以直接使用静态绑定(不能推广到final关键字上);第三种情况用于super关键字,在子类方法中强行指定调用父类方法时使用super指代父类,但此类的调用JVM使用动态绑定,具体不再赘述,参见《深入Java虚拟机》第19章的内容。
重载(Overload)
重载这个技术,仅仅是针对语言而言,而在JVM中其实并没有重载的概念。函数的调用匹配本身是通过函数名+函数参数确定的,在匹配函数名后,再匹配函数参数。在选择相同名字的方法时,编译器会选择一个最精确的重载版本。举个例子

public class Main {
        public int a = 1;
        public static void main(String[] args){
                new Main().test(3);
                new Main().test(new Integer(3));
                new Main().test(new ArrayList());
        }
        public void test(int a){System.out.println("int");}
        public void test(Object a){System.out.println("object");}
        public void test(List a){System.out.println("intlist");}
}

上面的方法会打印int int intlist。第一个调用匹配了同为int,Integer类型的函数;第二个调用在寻找Integer类型的参数未果的情况下,把Integer自动拆箱,匹配到了int类型;第三个调用则匹配了最精确的类型List,而不是不精确的Object。如果我们增加一个Integer类型作为参数的函数:

public class Main {
        public int a = 1;
        public static void main(String[] args){
                new Main().test(3);
                new Main().test(new Integer(3));
                new Main().test(new ArrayList<int>());
        }
        public void test(int a){System.out.println("int");}
                public void test(Integer a){System.out.println("integer");}
        public void test(Object a){System.out.println("object");}
        public void test(List<int> a){System.out.println("intlist");}
}

则会精确匹配类型,打印int integer intlist。
再看一个例子,思想来自于Java Puzzlers #46

public class Main {
        public int a = 1;
        public static void main(String[] args){
                new Main().test(null);
        }
        public void test(int[] a){System.out.println("intarray");}
        public void test(Object a){System.out.println("object");}
}

这次的输出会是什么?答案是intarray。如果我们调用的是new Main().test(new int[0]),那么结果总是很好理解;而如果传递一个null,则就有违直觉了。关键在于,编译器总是需要寻找更加精确的类型,而匹配的测试并没有使用实参(参数的实际类型)进行匹配。对于编译器来说,null都可以是Object类型变量和int[]类型变量的合法值,而int[]变量可以是Object类型,但Object类型变量不一定是int[]类型,相较之下int[]更为精确,所以编译器选择了int[]。如果一定要让编译器选择Object,那我们只需要通过new Main().test((Object)null)的形式即可。编译器在重载的时候只认形式参数,不认实际参数。
如何匹配重载函数的代码,Spring里有一个查找constructor的例子,在org.springframework.beans.factory.support.ConstructorResolver的autowireConstructor()函数,以前曾经在Spring配置的时候发生过问题Trace到这里。这个匹配算法使用了贪心的思想(代码里这么注释的),对于每个候选的构造函数都通过一个评估函数(getTypeDifferenceWeight())评估与调用参数类型(constructor-arg里配置的)的相似度,最后取最相似的候选函数。有兴趣的朋友可以自己看看。
隐藏(Hide)
隐藏的概念应用于父类和子类之间。子类的域、静态方法和类型声明可以隐藏父类具有相同名称或函数签名的域、静态方法和类型声明。同final方法不能被覆写类似,final的方法也不能被隐藏。考虑下面的代码:

class Point {
        int x = 2;
}
class Test extends Point {
        double x = 4.7;
        void printBoth() {
                System.out.println(x + " " + super.x);
        }
        public static void main(String[] args) {
                Test sample = new Test();
                sample.printBoth();
                System.out.println(sample.x + " " + ((Point)sample).x);
        }
}

上面的代码将打印4.7 2 \n 4.7 2。子类中的x变量隐藏了父类中的x变量,尽管两个变量类型不同。要访问父类中的变量必须使用super关键字或者进行类型强制转换。下面的代码展示了实例变量隐藏静态变量:

class Base{
        public static String str = "Base";
}
public class Main extends Base{
    public String str = "Main";
    public  static void main(String[] args){
        System.out.println(Main.str);
    }
}

上面的代码不能正确编译,原因是子类中的实例域把父类中的静态域给隐藏了。如果去掉子类中str的声明就可以正确编译。
静态方法和类型声明的隐藏的情况,和域隐藏的情况大同小异,不再给出具体的例子。
遮蔽(shadow)
遮蔽(shadow)指的是在一个范围内(比如{}之间),同名的变量,同名的类型声明,同名的域之间的名称遮蔽。我们最常见到的遮蔽就是IDE为我们自动生成的setter:

public class Pojo{
        private int x;
        public void setX(int x){
                this.x = x;
        }
}

即使是变量类型不相同也可以遮蔽。在上个例子中把setX(int x)的参数类型改为short,也能通过编译。函数的遮蔽常见于匿名类里的函数遮蔽原先类的函数等情况。
上面的几种遮掩比较常见,下面情况就比较特殊了(来源于Java Puzzlers#71)。其中Arrays.toString()方法提供了多个重载版本,可以方便的把基本类型数组转换为字符串。

import static java.util.Arrays.toString;
public class ImportDuty{
        public static void main(String[] args){
                printArgs(1, 2, 3, 4, 5);
        }
        static void printArgs(Object... args){
                System.out.println(toString(args));
        }
}

结果是不能编译,并且返回的信息告诉我们,Object.toString()方法不适用。这是为什么呢?原因在于,Object.toString()方法把Arrays.toString()方法给遮蔽了。下面的代码虽然可以编译,但运行时会提示找不到main方法入口:

class String{
}
public final class Main{
        public static void main(String[] args){
        }
}

原因在于我们自定义的String类型把java.lang.String类型遮蔽了。而如果强行加入import java.lang.String;语句,则不能通过编译。
遮掩(obscure)
遮掩很容易与遮蔽(shadow)混淆。其最重要的区别在于,遮蔽是相同元素之间的遮蔽,变量遮蔽变量,类型声明遮蔽类型声明,函数遮蔽函数。而遮掩却是变量遮掩类型和包声明,类型声明遮掩包声明。
遮掩的例子不多,看下面的例子,来自Java Puzzlers#68:

class ShadesOfGray{
        public static void main(String[] args){
                System.out.println(X.Y.Z);
        }
}
class X{
        static class Y{
                static String Z = "Black";
        }
        static C Y = new C();
}
class C{
        String Z = "White";
}

上面的例子不仅能通过编译,还将打印White。原因就在于当一个变量和一个类型具有相同名称,且具有相同作用域时,变量名优先级大。而类型的声明又具有比包声明更高的优先级。下面一个例子也不能通过编译:

public class Obscure{
        static String System;//变量遮掩了java.lang.System类型
        public static void main(String[] args){
                System.out.println("hello, obscure world!");
        }
}

小结
只有覆写是运行时的技术,另外其他的技术都是编译期的技术。
除了重载、覆写和作为setter的遮蔽,其他特性都强烈不推荐使用,这只会给你和你的继任者带来无穷无尽的麻烦,事实上,重载我认为也少用为妙,特别是结合了使用不确定参数个数的”…”函数的重载,会使你回头看代码时晕头转向。
p.s. 准备技术笔试/面试的语言类题目,C/C++的话,程序员面试宝典里有一些题目,另外就是C++的那些effective系列的书,具体还请C++达人列举(被点名的同学请自觉回帖)。Java的话,首推Joshua Bloch的Effecitve Java;第二就是Java Puzzlers,而后者其实更适合要参加技术笔试面试的同学。剩下的书,应该还有Practical Java,不过我没看过。

使用JMeter进行压力测试

JMeter是一款优秀的开源压力测试工具。网上有不少介绍的文章,比如DeveloperWorks上有一篇。不过脚本的制作比较麻烦,自己手动输入参数比较不实际。cnblog上有一测试达人在这里介绍了一个免费工具Badboy,可以录制在IE上的操作并导出JMeter脚本。Badboy本身是一个Web自动化测试工具。不足的是,一些Badboy录制的动作JMeter并不支持,比如多窗口操作。在导出jmx文件的时候Badboy会提示不能导出的内容。Badboy也有一些不够智能的地方,比如写死了服务器地址,而不是创建一个默认的HTTP访问器。
一开始使用JMeter进行压力测试完全是冲着JMeter的Cookie Manager功能去的。如果使用简单的工具,比如apache自带的ab工具,则要想方设法绕过应用程序的登录机制。使用Cookie Manager虽然不需要配置,但要注意的是,要把发来初始cookie的地址给包括进来。我测试的系统在/login.do这里发放cookie,但我直接访问/acegi_login登录,结果就丢掉了cookie,导致后续步骤失败。

Selenium, Eclipse, Junit折腾记

    很早以前就想搞自动化Web功能测试,知道了Selenium,看了些文档,但当真正到开发项目的关头,测试总是草草而过,跑完一遍手工的拉倒,回归测试更是无从谈起。前几天终于痛下决心写起使用Selenium的Web自动测试代码。

    先扯扯Selenium(字面上是“硒”的意思)。当初出来的时候结结实实把我震撼了一回。原来搞Web自动化测试基本上走的是GUI的那条老路(当然可能也是我当年孤陋寡闻),而这种GUI自动测试工具往往是功能强大的私有软件(比如WinRunner),另外对Web这种多变的测试元素用起来也是很别扭。Selenium另辟蹊径,从JS入手调用浏览器,同时允许通过跨平台的代码调用。从API就可以看出来这个东西的直观易用:

   1:  selenium.windowMaximize();
   2:  selenium.click("link=信息系统");
   3:  selenium.waitForPageToLoad("30000");
   4:  selenium.click("//a[contains(@href, 'projects.do?method:view&project.id=1')]");
   5:  selenium.waitForPageToLoad("30000");

    另外,Selenium还提供了一个Firefox插件-Selenium IDE,用于录制用户的操作(虽然部分动作无法录制)。录制的动作可以直接导出成HTML/Java/Ruby/C#/PHP等格式的代码,配合提供的SeleneseTestCase,当作JUnit的TestCase使用。

    不过这折腾就折腾在这TestCase上。Selenium的开发提供的SeleneseTestCase是Junit3风格的,放在JUnit4底下跑,JUnit4的Annotation功能就用不起来了(这点经我浏览代码查证)。Selenium要启动浏览器,如果用不上@BeforeClass的话,每次启动都初始化一下Selenium,开个IE或者Firefox,这个测试的效率可吃不消(也有比较麻烦的Workaround,但总觉得不是很好)。而甩开SeleneseTestCase的话,又舍不得那个在测试没有通过的时候自动截屏的功能。于是开始Google,兼看Junit4的源代码。

    最后终于在这里找到了方案。但问题又来了:Eclipse自带的Junit 4.3还没有这个方案需要的类(JUnit4ClassRunner )!自己手动把原来的Library移除换上Junit 4.5,又发现这个类才用了一个版本就Deprecated了。于是换上了新类(BlockJUnit4ClassRunner)。

    接下去的工作就是用上Decorator模式,把原来SeleneseTestCase的代码给移到新的BaseTestCase上。期间还遇上了一些Override的问题。上代码:

SeleniumTestListner类,用于拦截异常的抛出

   1:  public class SeleniumTestListener extends RunListener {
   2:   private SeleneseTestCaseAdapter stca;
   3:      @Override
   4:   public void testFailure(Failure failure) throws Exception{
   5:          Selenium selenium = stca.getSeleniumTestBase().getSelenium();
   6:   if(!stca.isCaptureScreenShotOnFailure()){
   7:   return;
   8:          }
   9:   if (selenium != null) {
  10:              String filename = failure.getDescription().getDisplayName() + ".png";
  11:   try {
  12:                  selenium.captureScreenshot(filename);
  13:                  System.err.println("Saved screenshot " + filename);
  14:              } catch (Exception e) {
  15:                  System.err.println("Couldn't save screenshot " + filename + ": " + e.getMessage());
  16:                  e.printStackTrace();
  17:              }
  18:          }
  19:   
  20:      }
  21:   
  22:   public void setSeleneseTestCaseAdapter(SeleneseTestCaseAdapter stca){
  23:   this.stca = stca;
  24:      }
  25:  }
 
SeleniumTestRunner类:加入SeleniumTestListener监听器,得到Test实例并注入监听器
   1:  public class SeleniumTestRunner extends BlockJUnit4ClassRunner  {
   2:   private SeleniumTestListener stl;
   3:   public SeleniumTestRunner(Class<?> c) throws Exception{
   4:          super(c);
   5:          stl = new SeleniumTestListener();
   6:      }
   7:   
   8:      @Override
   9:   public void run(RunNotifier rn){
  10:          rn.addListener(stl);
  11:          super.run(rn);
  12:      }
  13:   
  14:   /**
  15:       * Copy from BlockJUnit4ClassRunner.methodBlock(FrameworkMethod method)
  16:       * to get tested instance
  17:       * @author Marshall
  18:       */
  19:      @Override
  20:   protected Statement methodBlock(FrameworkMethod method) {
  21:          Object test;
  22:   try {
  23:              test= new ReflectiveCallable() {
  24:                  @Override
  25:   protected Object runReflectiveCall() throws Throwable {
  26:   return createTest();
  27:                  }
  28:              }.run();
  29:          } catch (Throwable e) {
  30:   return new Fail(e);
  31:          }
  32:   
  33:   //Marshall added
  34:          stl.setSeleneseTestCaseAdapter((SeleneseTestCaseAdapter)test);
  35:   
  36:          Statement statement= methodInvoker(method, test);
  37:          statement= possiblyExpectingExceptions(method, test, statement);
  38:          statement= withPotentialTimeout(method, test, statement);
  39:          statement= withBefores(method, test, statement);
  40:          statement= withAfters(method, test, statement);
  41:   return statement;
  42:      }
  43:  }

SeleniumTestCaseAdapter, 打上了@RunWith。所有的TestCase都继承这个Adapter。但这个Adapter并不继承JUnit的TestCase类

   1:  /**
   2:   * Decorator pattern which makes this class have the same capability as the
   3:   * SeleneseTestCase class had provided. Copy a lot of source code from the 
   4:   * decorated class.
   5:   * @author Marshall
   6:   */
   7:  @RunWith(SeleniumTestRunner.class)
   8:  public class SeleneseTestCaseAdapter {
   9:   private static SeleniumTestBase stb = new SeleniumTestBase();
  10:   private boolean isCaptureScreenShotOnFailure = false;
  11:   
  12:   /** Use this object to run all of your selenium tests */
  13:   protected static Selenium selenium;
  14:   
  15:      @BeforeClass
  16:   public static void setUpSelenium() throws Exception{
  17:          stb.setUp("http://127.0.0.1:8080/", "*iexplore");
  18:          selenium = stb.getSelenium();
  19:      }
  20:   
  21:      @AfterClass
  22:   public static void tearDownSelenium() throws Exception{
  23:          stb.tearDown();
  24:      }
  25:   
  26:     ......
  27:  }

样例测试类:

   1:  public class TestRiskRepo extends SeleneseTestCaseAdapter {
   2:   public TestRiskRepo(){
   3:          setCaptureScreenShotOnFailure(true);
   4:      }
   5:      @Before
   6:   public void set() throws Exception {
   7:          selenium.open("/apis/login.do");
   8:          selenium.type("j_username", "marshall");
   9:          selenium.type("j_password", "xxxx");
  10:          selenium.click("//input[@value='登录']");
  11:          selenium.waitForPageToLoad("30000");
  12:      }
  13:   
  14:      @Test
  15:   public void repo() throws Exception {
  16:          selenium.windowMaximize();
  17:          selenium.click("link=信息系统");
  18:          selenium.waitForPageToLoad("30000");
  19:          selenium.click("//a[contains(@href, 'projects.do?method:view&project.id=1')]");
  20:          selenium.waitForPageToLoad("30000");
  21:          verifyTrue(selenium.isTextPresent("xxx"));
  22:          selenium.click("link=组织风险库");
  23:          selenium.waitForPageToLoad("30000");
  24:          verifyEquals("组织风险库 | APIS", selenium.getTitle());
  25:          verifyTrue(selenium.isTextPresent("可能性"));
  26:          verifyFalse(selenium.isVisible("//button[contains(text(), '搜索')]"));
  27:          selenium.click("css=.x-tool");
  28:          verifyTrue(selenium.isVisible("//button[contains(text(), '搜索')]"));
  29:      }
  30:   
  31:  }

Hibernate里的重复记录

前几天APIS爆出了个奇怪的BUG:某个小组关闭任务后,任务自动被duplicate,然后子任务也丢失了。手工查看数据库,发现数据库里居然只有一条Task记录,Hibernate却返回两条。findById(id为主键)居然会返回两条记录,这就奇了怪了。
用Firebug看记录,发现抛出了异常。”More than one row with the given identifier was found”。可是数据库里明明只有一条记录啊。二话不说google发现了这个post,顿时想起来由于关联(association)而产生万恶的outer join。排查了几个关联(出问题的那个类偏偏是最heavily-associated),终于在一个与Part类<one-to-one>上发现了问题。虽然是<one-to-one>关联,却发现另外一方(Part)存在重复的记录,直接影响到Task类。找到原因,assign给Part类的责任人,问题解决。

Upgrading to Spring Security 2.0(zz)

原文:http://raibledesigns.com/rd/entry/upgrading_to_spring_security_2
就是appfuse作者的博客,做了一些精简
1. 包变化:org.acegisecurity  => org.springframework.security
2. 依赖变化(略,不用Maven)
3. tag标签的开头authz => security, 然后把taglib的关联项改为

<%@ taglib uri="http://www.springframework.org/security/tags"
    prefix="security" %>

4. web.xml,把<filter-class>改为org.springframework.web.filter.DelegatingFilterProxy,另外还要加上<init-param>标签

    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>springSecurityFilterChain</param-value>
    </init-param>

5. 修改security.xml,使用新的语法.根据作者的说法,AppFuse的security.xml的长度从177行下降到了33行,因为使用了很多convention over configuration的元素,如<http auto-config=”true”/>。关于语法,还需要一些实践把握。

java diff 及wiki相关

diff的原理在于找两个字符串之间的最大相同子串(Longest Common Subsequence)以及编辑距离,比较有名的实现是UnixLinux上常用的diff(GNU Diff)。

实现

Java里Diff的实现我找了一下,主要是两个,java-diff 和bsmi上的Diff ,前者为LGPL,后一个为GPL。其实代码也都不多,都实现了LCS算法。前一个协议上对我们比较有利,而且文档、测试和例子多一些。
JavaDiff里主要有两个类,Diff和Difference类。前者是算法,后者是差异的表示类。下面讲一下例子:

Object[] a = new Object[] {         "a",         "b",         "c",         "d",         "e"     };     Object[] b = new Object[] {         "a",         "x",         "y",         "b",         "c",         "j",         "e",     };     Difference[] expected = new Difference[] {         new Difference(1-1,  1,  2),         new Difference(3,  3,  5,  5),     };     Diff diff = new Diff(a, b);     List diffOut = diff.diff();

差别有三处,用两个Difference对象表示。一个Difference对象表示替换,增加,删除。Difference的构造函数:

public Difference(int delStart, int delEnd, int addStart, int addEnd)

如果delEnd或者addEnd为-1的话,就代表没有删除或者增加行为。
回到例子,两个字符串之间的差别在于,目标字符串在第1-2行(从0算起)增加了x,y,第3行的d被第5行的j替换。Difference虽然只说明了行号和动作,但我们可以推算出来增加了什么,删除了什么,替换了什么。下面是另一个更长的例子,来自测试用例:

public void testStrings1()     {         Object[] a = new Object[] {             "a",             "b",             "c",             "e",             "h",             "j",             "l",             "m",             "n",             "p"         };         Object[] b = new Object[] {             "b",             "c",             "d",             "e",             "f",             "j",             "k",             "l",             "m",             "r",             "s",             "t"         };         Difference[] expected = new Difference[] {             new Difference(0,  0,  0-1),             new Difference(3-1,  2,  2),             new Difference(4,  4,  4,  4),             new Difference(6-1,  6,  6),             new Difference(8,  9,  911),         };         runDiff(a, b, expected);     }

上面比较的都是一个个字符串的差异,推广一下,把每一行文本当作一个字母,就可以得到文件的差异。在java-diff的etc下有一个FileDiff.java,是一个很好的参考。得到之间的差异之后,我们要把这个差异表示出来,这个需要包装一下,不过难度不大。

版本保存

还有一个wiki版本的保存问题。大的维基引擎如MediaWiki(就是维基百科那个,顺便说一下,维基百科的英文版终于可以访问了)没时间研究,就是 JSPWiki也没来得及看)(JSPWiki连数据库也不用,Web用自己写的框架,可读性可能比较不行)。只研究了trac的wiki实现。trac 的wiki实现很简单,就是把每一个版本都保存在数据库,毕竟都是文本的,还可以接受。每次比较的时候就从数据库里取两个版本出来做一个diff,具体实 现在PYTHON/site-packages/trac/wiki/web_ui.py(_render_diff函数)。trac提供两种形式的 diff结果,一个是tabular的表格形式,就是很直观的对比,还有一个是Unified的形式,也就是经常看见的diff结果。这是通过页面上 javascript读table里的文字转换成Unified格式的diff文本,虽然个人不推荐这种方式。wiki的文本修改又有一个特点,就是每一 行其实内容可能比较多,只改了几个字,这样就要对这一行的两个版本再做一个diff,然后把删除的文本用<del>标签,增加的文本用 <ins>标签展示出来。
最后提一下JSR-170,一个用来管理仓库内容(主要是大型CMS)的API,支持版本控制,存储多元化,很复杂,有两个商业实现和一个Apache JackRabbit的开源实现,这里 是一个参考资料。JSR170也是里面的例子也是把每一个版本都存储下来。

参考资料

发现对hibernate的了解太少了

前几天赶工Virtualcampus,应用了一些hibernate的高级映射,比如<any>等。然后发现对unique, inverse, cascade的了解还是太少,基本上停留在会用工具生成代码的水平上,不知道怎么手写,对于性能上就更不懂了。刚翻出来hibernate_reference,补了一些东西。比如DetachedCriteria,  Bag和Set等等。

zz:Tomcat的中文乱码的经典解决方案

(转贴注:原来要用filter还有这么多的问题。要分别对待GET和POST方法。GET要在Connector里设好了URIEncoding(不是URLEncoding),今天被搞死了。。)

引言:
近期在做我们学校(华中科技大学)的注册中心的信息速递系统中又遇到了久违中文乱码的问题。由于注册中心的HUB系统需要团队开发完成,这导致中文 乱码问题尤为突出。万般无奈之下,硬下头皮寻根究底的找出问题的真正原因。皇天不负有心人,在查阅了N多资料和做了无数次试验之后,终于找到了个人认为较 为简单和经典的解决方案。
适用环境:
采用Tomcat 4.x或Tomcat 5.x,应用中出现中文乱码现象,且找不到统一的解决方案。
主要现象:
在应用中采用GET方式或POST方式时会出现中文乱码现象,而且针对这两种方式不能使用唯一的解决方案。一般您可能会使用以下的几种方案:
A.request.setCharacterEncoding(“gb2312”);
B.type = new String(type.getBytes(“ISO8859-1”), “GB2312”);
C.在Tomcat的配置文件Server.xml中的Connector标签中加入“URIEncoding=UTF-8”属性;
D.conf\web.xml 指定多个Encoding参数;
E.编写一个filter,处理Post提交的编码问题;
但有时候您可能会发现,这样做会让问题更糟糕。
问题分析:
要解决中文乱码的问题,首先要了解Tomcat在背后的处理机制,在查阅了许多资料以及许多前辈们写的文章并做了无数次试验之后,我找到了导致Tomcat中出现中文乱码的主要原因,即Tomcat对GET和POST两种方式的处理方法:在Tomcat 4.x时,采用的是GET和POST两种方式使用相同的字符集,也就是UTF-8。但在Tomcat 5.x下,GET和POST方式采用了不同的字符集
解决方案:
首先我想申明,我所给出的只是解决方案的一种,并不代表我给出的方案能解决大家所遇到的所有问题,但是有一点是肯定的,大家在同一个系统中必须使用相同的解决方案,如果可以最好统一您应用中各个组件包括数据库、JDK、WEB服务器等等的字符编码方案。
而我所采用的是下面的这种方案(只针对Tomcat):
Tomcat 4.x
1.页面编码采用“charset=gb2312”;
2.接收参数使用“request.setCharacterEncoding(“gb2312”);”;
3.如果需要在转换编码,如“type = new String(type.getBytes(“ISO8859-1”), “GB2312”);”;
Tomcat 5.x
1.在配置文件Server.xml的Connector标签中加入“useBodyEncodingForURI=”true””,这个属性主要 是让Tomcat 5.x在GET和POST方式使用相同的字符集。如果这样做仍不能解决问题,您可以尝试再加入另一个属性“URIEncoding=UTF-8”;
2.其他处理同Tomcat 4.x
以上方法可做参考,如果您有其他好的方法或觉得本文中有任何问题或遗漏欢迎批评斧正。