在河里抓鱼的熊

抓鱼杂谈

Archive for the ‘经验之谈’ Category

解决一个jUnit运行时错误

without comments

情况概述:

已经经过测试、运行过多遍的一个jUnit测试用例突然不能用了,运行时提示:

java.lang.Exception: Method setupOnce() should be static
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)

代码如下:

@RunWith(Parameterized.class)
public class SqlIndexLogicTest {

	Object[] paramNow;

	private String expected;
	private JSONArray target;

	@Parameters
	public static Collection<object []> params() throws JSONException{
		return Arrays.asList(new Object[][]{
				{"SELECT * FROM (SELECT REQUEST_TIME,RESULT_ID,KEY_CODE,REGION_CODE FROM SD_UKMS_LOG GROUP BY REQUEST_TIME,REGION_CODE,KEY_CODE,RESULT_ID ORDER BY REGION_CODE) WHERE 1=1 and request_time>to_date('2011-09-21', 'yyyy-mm-dd')",
					new JSONArray("[1506, 0, 5, {'request_time':['2011-09-21']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931",
					new JSONArray("[1510, 0, 5, {'region_code':['931']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code in ('931','000')",
					new JSONArray("[1510, 0, 5, {'region_code':['931','0000']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code in ('931','0000') and request_time< =to_date('2012-01-01', 'yyyy-mm-dd')",
					new JSONArray("[1510, 0, 5, {'region_code':['931','0000'], 'request_time':['2012-01-01']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG)",
					new JSONArray("[1509, 0, 5]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1",
					new JSONArray("[1509, 0, 5, {'region_code':[], 'cas_id':[], 'request_time':[]}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1",
					new JSONArray("[1509, 0, 5, {'region_code':[''], 'cas_id':[''], 'request_time':['']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931",
					new JSONArray("[1509, 0, 5, {'region_code':['931'], 'cas_id':[''], 'request_time':['']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931 and cas_id=13519000753",
					new JSONArray("[1509, 0, 5, {'region_code':['931'], 'cas_id':['13519000753'], 'request_time':['']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931 and cas_id=13519000753 and request_time<=to_date('2011-09-21', 'yyyy-mm-dd')",
					new JSONArray("[1509, 0, 5, {'region_code':['931'], 'cas_id':['13519000753'], 'request_time':['2011-09-21']}]")},
		});
	}

	public SqlIndexLogicTest(String expected, JSONArray target){
		this.target = target;
		this.expected = expected;
	}

	@BeforeClass
	public void setUpOnce() throws JSONException{
	}

	@AfterClass
	public static void tearDownOnce(){

	}

	@Before
	public void init() throws JSONException{
		JSONObject conditionObj = target.optJSONObject(3);
		Map<String, String[]> whereParams = new TreeMap<string , String[]>();
		if(conditionObj != null){
			@SuppressWarnings("unchecked")
			Iterator</string><string> it = conditionObj.keys();
			while(it.hasNext()){
				String key = it.next();
				System.out.println("key:"+key);
				JSONArray subCondArr = conditionObj.getJSONArray(key);
				String[] subCondArrStr = new String[subCondArr.length()];
				for(int j=0; j<subcondarr .length(); j++){
					subCondArrStr[j] = subCondArr.getString(j);
				}
				whereParams.put(key, subCondArrStr);
			}
		}
		paramNow = new Object[]{target.get(0), target.get(1), target.get(2), whereParams, target.optJSONArray(4)};
	}

	@After
	public void destroy(){
	}

	@Ignore("pause this method test")
	@Test
	public void queryNormal() throws DAOException, JSONException{
		org.json.JSONObject jo = SqlIndexLogic.getInstance()
				.query((Integer) paramNow[0], (Integer) paramNow[1], (Integer) paramNow[2], (Map<String, String[]>) paramNow[3], (JSONArray) paramNow[4]);
		System.out.println(jo);
	}

	@Test
	public void buildWrapedSQL() throws DAOException, JSONException{
		String baseSQL = SqlIndexLogic.getInstance().getBaseSQL((Integer) paramNow[0]);
		StringBuilder sql = SqlIndexLogic.getInstance()
				.buildWrapedSQL(baseSQL, (Integer) paramNow[0], (Integer) paramNow[1], (Integer) paramNow[2], (Map<string , String[]>) paramNow[3], (JSONArray) paramNow[4]);
		System.out.println(sql);
		assertEquals(expected, sql.toString());
	}

}

解决方法:

将setupOnce()方法改成static修饰即可。

原因:

@BeforeClass和@AfterClass注解的两个方法需要静态化,以符合jUnit调用机制。而那个@BeforeClass注解的setupOnece()方法的static修饰符不知道什么时候不小心删掉了。

Written by qianxiong

January 18th, 2012 at 4:44 pm

Posted in 技术,笔记,经验之谈

Tagged with ,

Failed to load unit ‘HGCM’

without comments

现象:

恢复运行virtualBox虚拟机时,出现如下错误

Failed to load unit 'HGCM' (VERR_SSM_UNEXPECTED_DATA)

日志最后几行如下

00:00:03.757 Changing the VM state from ‘DESTROYING’ to ‘TERMINATED’.
00:00:03.895 ERROR [COM]: aRC=NS_ERROR_FAILURE (0×80004005) aIID={1968b7d3-e3bf-4ceb-99e0-cb7c913317bb} aComponent={Console} aText={Failed to load unit ‘HGCM’ (VERR_SSM_UNEXPECTED_DATA)}, preserve=false
00:00:03.895 Power up failed (vrc=VERR_SSM_UNEXPECTED_DATA, rc=NS_ERROR_FAILURE (0X80004005))

解决办法:

销毁(Discard)当前虚拟机状态,然后启动即可

原因:

早上升级了virtualBox版本,但是在升级之前,虚拟机不是shutDown状态,而是saveState状态,而新版本虚拟机在识别、启动保存的虚拟机状态时跟上

Written by qianxiong

December 12th, 2011 at 4:53 pm

Posted in 笔记,经验之谈

Tagged with ,

转换整个项目编码

without comments

处于种种原因,我们需要将项目由原来的GBK编码改成UTF-8编码。如果针对单个文件,我们可能的做法是:将文件按原来的方式打开,copy所有内容,然后将文件编码指定为UTF-8,重新打开,CTRL+A, 接着CTRL+V。但项目文件好几百个,甚至上千个。这种方法显然行不通。

可喜的是,Linux有着非常强大的功能,只要操作得当,必能将人从繁重的工作中解脱出来。高效的做法是:

find . -regex ".+\.\(tpl\|js\|jsp\|css\|java\|xml\)$" | xargs enca | grep 'GB2312' | awk -F: '{print $1}' | xargs enconv

以上命令在我的运行过程中情况如下,转换435个文件,用时不超过5秒。

以上命令有两个前提:

  1. 当前环境的编码是UTF-8
  2. 已经安装了enca包

如果上面前提不成立,则需要按如下处理:

  1. 针对1不成立,修改命令如下:
    find . -regex ".+\.\(tpl\|js\|jsp\|css\|java\|xml\)$" | xargs enca | grep 'GB2312' | awk -F: '{print $1}' | xargs enconv -x UTF-8
  2. 针对2不成立,则安装enca包。如ubuntu:
    sudo apt-get install enca
还是那句老话:有玉砸玉,有砖拍砖!

Written by qianxiong

November 30th, 2011 at 7:17 pm

Posted in 技术,笔记,经验之谈

Tagged with , ,

更新ear/jar包内部部分内容

without comments

地球人都知道ear项目打包发布很麻烦,有时候就一个字的更改,也得要重新导出(Export)整个项目,耗时又费力。倘若能直接更新指定的那个jsp/class/js/css/png文件,而且只需几秒钟,觉得这个方案很好的有木有?那好,我就把尝试N多次终于成功的方法告诉你,有钱的捧个钱场,没钱的捧个人场!

关键语法:

jar uf xxxx.ear absolutePath/filename

一般步骤:

  1. 如果没有子路径,那就直接把替换文件拿过来,放在当前路径下,按上面语法执行;
  2. 如果有子路径,比如META-INF/则先建立这个目录路径。更方便的方式是解压出那个指定的文件,这样就自动生成了子路径。语法如下:
    jar xf xxxx.ear absolutePath/filename

    然后把替换文件放到相应位置,或者直接在解压出来的文件上编辑,完成后按上面语法执行;

Examples:

单层压缩包:

  1. 由于采用了新版的log4j,现在需要更新NGBBoss.ear根目录下的log4j.jar:
    jar uf NGBBoss.ear log4j.jar
  2. 更新NGBBoss.ear中META-INF/目录的application.xml文件:
    jar uf NGBBoss.ear META-INF/application.xml

    注意:外部文件application.xml也得要有一个对应的父目录META-INF/与执行jar命令时的当前路径下。也就是说,当前路径下要有NGBBoss.ear META-INF/ 这两个东东,而application.xml在META-INF/下。

多层压缩包更新:

  1. 如果要更新ear包内的war包中的某个jsp/jpg/html/css/js怎么办?那就只能麻烦一点:
  2. 先把war包取出来;
  3. 再把那个jsp/jpg/html/css/js文件解压出来;
  4. 修改/替换;
  5. 按单层压缩包的更新方式把war包更新了;
  6. 按单层压缩包的更新方式把ear包更新了;

Written by qianxiong

August 15th, 2011 at 12:44 pm

解锁Oracle表

without comments

两句话:

  1. 查询被锁表的相关信息
    select
      se.username, obj.object_name, obj.object_id, lk.session_id, se.serial#
    from
      user_objects obj,
      v$locked_object lk,
      v$session se
    where
      lk.object_id=obj.object_id
      and
      lk.session_id=se.sid;
  2. 解锁
    alter system kill session '{sid},{serial#}';

Written by qianxiong

July 29th, 2011 at 5:51 pm

在旧有项目上开发新功能

without comments

背景:

公司有一套项目的统一版本,一个由6个子项目组成的ejb项目,子系统分别是:commons, dao, bussiniselogic, ejbFacade, delegate, webapp。如果更改了一个jsp页面还好说,把jsp上传到服务器即可;但如果是更改了Action,那就麻烦了:重新打包、发布、重启服务器。项目大了打包发布就很耗时,如果大家很有时间,还打算利用这段时间喝杯咖啡,聊两句,那就无所谓了。如果很着急,或者急于看到自己的更改结果。那最好另想办法让你的程序编译-打包-发布过程快一点!

实践:

下面的方法经是我在经过实践证明可行的:

  1. 新建一个动态web项目。

    命名为webappNew。在build path(如src/main/java/)下对应建立相应项目的名称(commons/, dao/, bussiniselogic/, webapp/)。更改build path,将以上新建的几个子目录设置为build path。别忘了把该有的项目引用和依赖加入进来!

  2. 只在这个新项目下工作。

    开发过程中,将本该放在子项目中的新代码放在这些对应目录之下,如dao下要新增一个类com.maywide.oss.web.util.mkuser.java, 此代码本该新增在项目dao下,但是现在放在此web项目的dao/目录下。这样的目的是不再对dao项目重新编译打包,发布的时候只对此新项目编译、打包。这样能节约很多时间。

  3. 重构/集成

    项目开发完毕,需要对整个项目重构。有了前面新建的对应项目名称的目录,重构将非常简单。只需要将各个目录下的代码copy到对应项目中去就行了!然后删除在第1步中新建的web项目。

问题:

  1. 原web项目不参与编译,新页面怎么运行?

    原有的web项目中带有登录功能、js库、css库、框架设计、BaseAction、BaseAF等等基础内容。如果新建的项目中是完全干净的,所有代码都是新开发的,那没有了这些支撑新web页面/功能正常运行的基础设施,新的web项目是无法正常运行甚至不能运行的。所以,这里有了一个方案:

    1. 新建一个J2EE Utility Project,命名为webappU。将原web项目中的build path复制到webappU,并设置相同的build path,包括resource。
    2. 将webappU包含到webappNew的Java EE module Dependencies中。这样新开发的Action/AF/Bean就可以引用到原有的基础框架。
    3. 将css/、common/、images/、inc/、js/、WEB-INF/、index.jsp等顶层文件拷贝到webappNew中。其它功能按需添加。
  2. 如何进行团队协作?

    以前我们直接使用svn,但是现在webappNew是要被删除的,如何协作呢?Git是一个不错的选择。尤其对于连接服务器不方便但又需要团队协作的情况下!

 

Insert mode

Written by qianxiong

July 2nd, 2011 at 4:48 pm

WikiEditor插件安装配置

without comments

我自己有个meidiaWiki,这是为了积累知识所做的笔记,通过wiki的方式组织,强迫自己将笔记做得正规些,有条不紊,时机成熟的时候再放到网上。

为什么安装WikiEditor?

写wiki最烦恼的是格式,尤其是那些枯燥又容易混淆的格式符号。如果你用过wiki,几个月没有用了,还能记得非数字列表怎么表示吗?

去年听说wiki老总立志要让编辑人员更容易使用。最近看到meidawiki官方网站界面变得更加漂亮了,编辑页面也有了很大的变化,以往那些单调的编辑功能变得丰富了,而且还能插入特殊字符、有帮助,编辑的确更加容易。所以我打算把这个用到我的wiki上。

 

安装

我的安装过程并非一帆风顺。下载新的wiki系统并执行了升级程序后,并没有看到那漂亮的编辑器。全新安装也不行。于是仔细检查官方网站,发现这是mediaWiki的一个extension,叫UsabilityInitiative,下载与自己wiki版本对应的程序版本,需要翻墙。

最简单的流程是按照官方页面给出的方法,省时省力,又不费脑细胞。别像我一般傻,折腾一大堆,最后才恍然大悟!

官方方法:

  1. Get the extension with distributor or svn and drop it into MediaWiki directory /extensions
  2. Setup your LocalSettings.php as explained in README file.
  3. Run php maintenance/update.php from the command line (see update.php and also here)

This is an example, stable configuration for your LocalSettings.php file for the 1.16 release version:

// UsabilityInitiative/Vector
require_once("$IP/extensions/UsabilityInitiative/Vector/Vector.php");
$wgVectorModules['editwarning']['global'] = false; // Don't enable EditWarning globally
$wgVectorModules['editwarning']['user'] = true; // Allow users to enable EditWarning in their preferences
$wgVectorUseSimpleSearch = true; // Need this as well for SimpleSearch
$wgDefaultSkin = 'vector'; // If you want to change the default skin for new users
$wgVectorUseIconWatch = true; //Enable star icon to add/remove page from watchlist
// UsabilityInitiative/WikiEditor
require_once("$IP/extensions/UsabilityInitiative/WikiEditor/WikiEditor.php");
$wgDefaultUserOptions['usebetatoolbar-cgd'] = 1;  // Default user preference to use toolbar dialogs
$wgWikiEditorModules['toolbar']['global'] = true;  // Enable the WikiEditor toolbar for everyone
$wgWikiEditorModules['toolbar']['user'] = false;  // Don't allow users to turn the WikiEditor toolbar on/off individually

接下来我讲讲我的过程:

  1. 读README文件。我当时没有找到这个页面,而是找到了WikiEditor页面,结果走了一大圈弯路(我好傻,居然没有看到上面的醒目提示,说This extension has been migrated from Extension:UsabilityInitiative.)。我当时看到那个页面上说按README做,而页面链接过去的README是svn最新版本的,不是1.16的,结果怎么都不行。后来看自己目录下的READEME才隐约领会到,最后几乎把整个README内容都copy到了LocalSetting.php里面(找对书籍和信息很重要,不然会误入歧途!)。
  2. 执行SQL。当时我以为把LocalSetting.php设置对了就搞定了,没想到到页面上设置的时候却报SQL错误,说表不存在。半天没有找到初始化配置页面,只能动手登录mysql,然后手动执行那些找出来的sql文件。sql文件在哪?在extensions/UsabilityInitiative/下find一下吧。

配置

  1. 在“特殊页面”中找到“Wiki数据和工具”一节,点“启用Usability Initiative”
  2. (可选)在“设置>参数设置>编辑”中找到“新功能测试”,然后按喜好设置即可配置选项

过程很简单,但不要走错,错了就复杂了!

Written by qianxiong

April 8th, 2011 at 3:39 pm

VIM查找时需要转义的字符

without comments

首先,VIM中在查找时使用正则表达式进行。但是,不能将平常的正则表达式直接搬过来使用。原因是标准正则中有些符号与VIM中的一些固有符号有冲突,这时就需要对这些符号转义,才能表示这个符号表示正则表达式的操作符,否则表示VIM操作符或者字符本意。

对于这些将在VIM Search时需要转义的字符总结如下:

  1. |
  2. &
  3. ()
  4. +
  5. =
  6. ?
  7. \{n,m}  n到m个,按最多匹配
  8. \{-n,m}  n到m个,按最少匹配
  9. <>  匹配单词边缘

Written by qianxiong

September 16th, 2010 at 10:37 am

Posted in 经验之谈

Tagged with , ,

命令行下批处理图片

without comments

每次拍完照片之后,因为照片太大无法直接上传到空间里面,需要将照片缩小。这时总是因为工作量太大而无法让人望而生畏。如果使用windows,而且手头又有photoshop,那很好。因为photoshop里面有个批处理功能叫Action,但是没有在windows下,更没有photoshop,有没有简便的方法可以做这种简单重复的事情呢?
答案是肯定的。那就是命令行的”imagemagick”!imagemagick是一组命令,包括convert, identify, mogrify, composite, montage, compare, stream, display, animate, import, conjure.
使用convert命令对原图做缩放处理,并将缩放后的图片放到resized/目录中:

find . -maxdepth 1 -name '*.JPG' -exec convert {} -resize '1024x1024' resized/{} \;

某些网站的图片上传功能要求后缀名小写,这时需要做一次转换。使用如下命令:

for f in *; do mv $f `echo $f | tr '[:upper:]' '[:lower:]'`; done

或者使用如下命令(更简单):

find resized -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;

详情请看这里这里

Written by qianxiong

July 28th, 2010 at 5:56 pm

一点关于西厢计划安装配置的问题

without comments

对于崇尚自由的人们来说,西厢计划简直就是神来之笔,它让我们在这个黑暗的年代看到了曙光,为我们打破信息谎言创造了途径。最值得赞叹的是:这是规则之下的博弈。gfw今天棋逢对手了!

具体的编译、安装和配置请看http://code.google.com/p/scholarzhang/wiki/INSTALL
配置请看http://code.google.com/p/scholarzhang/wiki/USAGE
为了不重复发明轮子。在此,我仅仅提供一些应对问题的经验。

如果按照官方文档的指引,基本能够成功,但是从留言和我自己的经历来看,在中途可能还是有一部分人会遇到一点问题而成功不了。这些问题官方文档没有提及,也许是因为软件作者本身水平太高,这些问题基本遇不到,或者顺手就解决了,也就没有正式的写道wiki中。

好了,说正题。

  1. 问题一。大部分朋友会遇到找不到下面三个文件:
    /lib/xtables/libipt_ZHANG.so
    /lib/xtables/libipt_CUI.so
    /lib/xtables/libipt_gfw.so
    到/lib/xtables/下的确找不到这三个文件。不过编译安装后我们可以在/usr/local/libexec/xtables/下找到文件名相似的个文件(而源文件目录下,生成的也只有这几个文件,可以断定就是他们几个)。因此,可以从/usr/local/libexec/xtables/下创建链接文件到/lib/xtables/下:
    cd /lib/xtables
    sudo ln -s /usr/local/libexec/xtables/libxt_CUI.so libipt_CUI.so
    sudo ln -s /usr/local/libexec/xtables/libxt_ZHANG.so libipt_ZHANG.so
    sudo ln -s /usr/local/libexec/xtables/libxt_gfw.so libipt_gfw.so
  2. 问题二,IP地址解析仍然不正确。即使正确设置了自己的DNS server为8.8.8.8也解析不到正确的www.youtube.com地址。反复检查了/etc/hosts文件以及/etc/resolv.conf文件都找不到哪里出了错。于是浏览器要么出现网络错误,要么出现refuse错误。如果使用命令
    nslookup www.youtube.com

    则一如既往的错。但如果使用nslookup的交互模式,并设置了dns server 为8.8.8.8,则能够解析到正确的IP地址。其实,这是因为在还没有执行ipset和iptables命令之前访问了youtube,那时DNS被污染,因此本地的DNS cache记录了被污染的DNS地址。通过执行下面的命令可以清除本地DNS Cache:

    sudo resolvconfig -d wlan0
    sudo resolvconfig -d eth0
    
  3. ipset的规则是什么,example中的规则怎么理解?
    example中的规则一般如这样”-A YOUTUBE 208.117.224.0/19 ”。前面的-A和YOUTUBE 容易理解。但是”208.117.224.0/19″是什么?其实,这是表示一个网段,将208.117.224.0转化为二进制后应该是”11010000.01110101.1110000.00000000″,每段8位,第19位是第三段第三位,就是最后那个1上。”208.117.224.0/19″就表示二进制的前19位是相同的,这表示一个网段的IP地址。与子网掩码表示的方法不同,但意思是一样的。

每个步骤保证正确之后,可以写成一个脚本,以简便每次启动过程:

首先,导出刚才的那些设置:

sudo ipset -S > ipset.ruls
sudo iptables-save > iptable.ruls

接着,把它们放到/etc目录下面,(不能放到/home目录下,因为到时候会用sudo执行,root用户访问不到自己的目录):

sudo mkdir /etc/west-chamber
sudo chmod a+w /etc/west-chamber
cp ipset.ruls iptable.ruls /etc/west-chamber/

最后,写一个脚本,并赋予可执行权限:

sudo touche /usr/local/sbin/west-chamber.sh
#!/bin/sh
#restore rules
ipset -R < /etc/west-chamber/ipset.ruls
iptables-restore < /etc/west-chamber/iptable.ruls
#set trusted nameserver
echo nameserver 8.8.8.8 > /etc/resolv.conf
echo nameserver 8.8.4.4 >> /etc/resolv.conf

#clear the resolve catch
resolvconf -d wlan0
resolvconf -d eth0
sudo chmod a+x /usr/local/sbin/west-chamber.sh

现在就可以执行

sudo west-chamber.sh

来启动西厢计划了。

现在打开youtube看看吧!

附A:一些有用的命令:

查询所有www.youtube.com的IP并保存到文件中:

$nslookup www.youtube.com | grep Address | grep -v '#53' | awk '{print $2}' >> www.youtube.com.ip

清除本地的dns缓存:

$resolvconfig -d wlan0
$resolvconfig -d eth0

统计有多少个IP:

$cat www.youtube.com.ip | sort -g | uniq | wc -l

Written by qianxiong

March 21st, 2010 at 5:19 pm