随笔 - 10, 文章 - 0, 评论 - 7, 引用 - 0
数据加载中……

2007年5月20日

关于appfuse的探讨

      项目在紧张的生活中很快就要结束了,一直以来,都是由于工作的繁忙,没有时间去写自己的blog.这段时间终于可以稍微闲下来了,觉得应该把这一阶段的工作总结一下了,但是拿起键盘,思绪却有些散乱,想写的很多,却也不知道怎么来写,还是慢慢来吧.
   首先说一下整体框架appfuse,它是集成了当今最流行的web应用框架的更高层次的web开发框架.也可以说是一个web开发的基本平台,它将当前最流行的一些技术,比如hibernate,spring,struts,DBUnit,ant,log4j,xdoclet,sitemesh,velocity,jstl,webwork,等等都集成了进来,在国内能够将这些都应用进来的并不多见.

待续....

posted @ 2007-06-07 23:56 LiuTing 阅读(240) | 评论 (0)编辑 收藏

JS常见函数

1.常规函数  
  javascript常规函数包括以下9个函数:  
  (1)alert函数:显示一个警告对话框,包括一个OK按钮。  
  (2)confirm函数:显示一个确认对话框,包括OK、Cancel按钮。  
  (3)escape函数:将字符转换成Unicode码。  
  (4)eval函数:计算表达式的结果。  
  (5)isNaN函数:测试是(true)否(false)不是一个数字。  
  (6)parseFloat函数:将字符串转换成符点数字形式。  
  (7)parseInt函数:将符串转换成整数数字形式(可指定几进制)。  
  (8)prompt函数:显示一个输入对话框,提示等待用户输入。例如:  
  (9)unescape函数:解码由escape函数编码的字符。  
  2.数组函数  
  javascript数组函数包括以下4个函数:  
  (1)join函数:转换并连接数组中的所有元素为一个字符串。例:  
 
 程序代码  
    function JoinDemo()  
    {  
     var a, b;  
     a = new Array(0,1,2,3,4);  
     b = a.join("-");//分隔符  
     return(b);//返回的b=="0-1-2-3-4"  
    }   
   
 (2)langth函数:返回数组的长度。例:  
       
 程序代码  
function LengthDemo()  
    {  
     var a, l;  
     a = new Array(0,1,2,3,4);  
     l = a.length;  
     return(l);//l==5  
    }   
 
  (3)reverse函数:将数组元素顺序颠倒。例:  
      
 程序代码  
function ReverseDemo()  
   {  
    var a, l;  
    a = new Array(0,1,2,3,4);  
    l = a.reverse();  
    return(l);  
   }   
  (4)sort函数:将数组元素重新排序。例:  
       
 程序代码  
function SortDemo()  
    {  
     var a, l;  
     a = new Array("X" ,"y" ,"d", "Z", "v","m","r");  
     l = a.sort();  
     return(l);  
    }   
  3.日期函数  
  javascript日期函数包括以下20个函数:  
  (1)getDate函数:返回日期的“日”部分,值为1~31。例:  
      
 程序代码  
function DateDemo()  
   {  
    var d, s = "Today's date is: ";  
    d = new Date();  
    s += (d.getMonth() + 1) + "/";  
    s += d.getDate() + "/";  
    s += d.getYear();  
    return(s);  
   }   
  (2)getDay函数:返回星期几,值为0~6,其中0表示星期日,1表示星期一,...,6表示星期六。例:  
      
 程序代码  
function DateDemo()  
   {  
    var d, day, x, s = "Today is: ";  
    var x = new Array("Sunday", "Monday", "Tuesday");  
    var x = x.concat("Wednesday","Thursday", "Friday");  
    var x = x.concat("Saturday");  
    d = new Date();  
    day = d.getDay();  
    return(s += x[day]);  
   }   
  (3)getHouse函数:返回日期的“小时”部分,值为0~23。例。  
      
 程序代码  
function TimeDemo()  
   {  
    var d, s = "The current local time is: ";  
    var c = ":";  
    d = new Date();  
    s += d.getHours() + c;  
    s += d.getMinutes() + c;  
    s += d.getSeconds() + c;  
    s += d.getMilliseconds();  
    return(s);  
   }   
  (4)getMinutes函数:返回日期的“分钟”部分,值为0~59。见上例。  
  (5)getMonth函数:返回日期的“月”部分,值为0~11。其中0表示1月,2表示3月,...,11表示12月。见前面的例子。  
  (6)getSeconds函数:返回日期的“秒”部分,值为0~59。见前面的例子。  
  (7)getTime函数:返回系统时间。  
      
 程序代码  
function GetTimeTest()  
   {  
    var d, s, t;  
    var MinMilli = 1000 * 60;  
    var HrMilli = MinMilli * 60;  
    var DyMilli = HrMilli * 24;  
    d = new Date();  
    t = d.getTime();  
    s = "It's been " 
    s += Math.round(t / DyMilli) + " days since 1/1/70";  
    return(s);  
   }   
  (8)getTimezoneOffset函数:返回此地区的时差(当地时间与GMT格林威治标准时间的地区时差),单位为分钟。  
      
 程序代码  
function TZDemo()  
   {  
    var d, tz, s = "The current local time is ";  
    d = new Date();  
    tz = d.getTimezoneOffset();  
 
    if (tz 的链接点的名称,另一个函数link设定的URL地址。  
  (2)big函数:将字体加到一号,与...标签结果相同。  
  (3)blink函数:使字符串闪烁,与...标签结果相同。  
  (4)bold函数:使字体加粗,与...标签结果相同。  
  (5)charAt函数:返回字符串中指定的某个字符。  
  (6)fixed函数:将字体设定为固定宽度字体,与...标签结果相同。  
  (7)fontcolor函数:设定字体颜色,与标签结果相同。  
  (8)fontsize函数:设定字体大小,与标签结果相同。  
  (9)indexOf函数:返回字符串中第一个查找到的下标index,从左边开始查找。  
  (10)italics函数:使字体成为斜体字,与...标签结果相同。  
  (11)lastIndexOf函数:返回字符串中第一个查找到的下标index,从右边开始查找。  
  (12)length函数:返回字符串的长度。(不用带括号)  
  (13)link函数:产生一个超级链接,相当于设定的URL地址。  
  (14)small函数:将字体减小一号,与...标签结果相同。  
  (15)strike函数:在文本的中间加一条横线,与...标签结果相同。  
  (16)sub函数:显示字符串为下标字(subscript)。  
  (17)substring函数:返回字符串中指定的几个字符。  
  (18)sup函数:显示字符串为上标字(superscript)。  
  (19)toLowerCase函数:将字符串转换为小写。  
  (20)toUpperCase函数:将字符串转换为大写。  
 
//校验是否全由数字组成  
 
 程序代码  
function isDigit(s)  
{  
var patrn=/^[0-9]{1,20}$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串  
 
 程序代码  
function isRegisterUserName(s)  
{  
var patrn=/^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验用户姓名:只能输入1-30个以字母开头的字串  
 
 程序代码  
function isTrueName(s)  
{  
var patrn=/^[a-zA-Z]{1,30}$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验密码:只能输入6-20个字母、数字、下划线  
 
 程序代码  
function isPasswd(s)  
{  
var patrn=/^(\w){6,20}$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-”  
 
 程序代码  
function isTel(s)  
{  
//var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?(\d){1,12})+$/;  
var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验手机号码:必须以数字开头,除数字外,可含有“-”  
 
 程序代码  
function isMobil(s)  
{  
var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验邮政编码  
 
 程序代码  
function isPostalCode(s)  
{  
//var patrn=/^[a-zA-Z0-9]{3,12}$/;  
var patrn=/^[a-zA-Z0-9 ]{3,12}$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
//校验搜索关键字  
 
 程序代码  
function isSearch(s)  
{  
var patrn=/^[^`~!@#$%^&*()+=|\\\][\]\{\}:;\'\,.<>/?]{1}[^`~!@$%^&()+=|\\\][\]\{\}:;\'\,.<>?]{0,19}$/;  
if (!patrn.exec(s)) return false 
return true 
}  
 
 程序代码  
function isIP(s) //by zergling  
{  
var patrn=/^[0-9.]{1,20}$/;  
if (!patrn.exec(s)) return false 
return true 
}  

posted @ 2007-05-29 18:03 LiuTing 阅读(291) | 评论 (0)编辑 收藏

DISPALYTAG的用法

1) Caption指标题
2) name:数据源
3) defaultsort:排序列,一般不需要
4) defaultorder:排序方式
5) class:表格显示的样式
6) varTotals:需要统计(总计)
7) decorator="totals":表示使用指定的decorator。现在仅在同时使用小计、合计时用到,自己也可以定制。
8) <display:column 部分:
  property指数据中的列名
  title指显示的标题
  format指数据的格式化样式,
  total表示该字段是否需要作统计。
  style="width:20%"可以设置宽度。
  也可以用
  <display:column title="请假时间">
  <fmt:formatDate value="${currentRowObject.QJSJ}" pattern="yyyy-MM-dd"/>
  </display:column>表现。
  group="1"表示进行group的字段。
9) 在表格中建立一个连接:
  <display:column title="部别">
  <a href="<c:out value="${pageContext.request.contextPath}"/>
  /holidayDefaultSearchStatView.do?type=
  <c:out value="${currentRowObject.CJDM}"/>&SYD=
  <c:out value="${currentRowObject.DWDM}"/>" target="_blank">
  <c:out value="${currentRowObject.DWMC}"/>
  </a>
  </display:column>
10) 由于该tag只有合计时,没有合计那一列,所以实现的时候以<display:footer>实现。
  可以通过totalMap对象获取合计的数据,如totalMap.colomn1获取合计的第一列数值。
11) 标题头不显示<display:setProperty name="basic.show.header" value="false" />
12) 自己定制表头:<display:caption>
   使用decorator:
   <jsp:scriptlet>
   org.displaytag.decorator.TotalTableDecorator totals =
   new org.displaytag.decorator.TotalTableDecorator();
       totals.setTotalLabel("合计");
       totals.setSubtotalLabel("小计");
       pageContext.setAttribute("totals", totals);
   </jsp:scriptlet>
实例:
<display:caption>这是表标题</display:caption>
 <display:table
  name="${model.rowToColumnStatResult}" defaultsort="1"
  class="simple" defaultorder="ascending" varTotals="totalMap">
  <display:column property="GCWZ" title="部门"/>
   <c:forEach items="${model.YList.YList}" var="yColumn">
   <c:forEach items="${model.rowToColumnStatResult[0]}"
      var="mapEntry">
    <c:if test="${mapEntry.key == yColumn}">
     <th>
       <display:column property="${mapEntry.key}"
       title="${mapEntry.key}"
       format="{0,number,####.##}" total="true"/>
     </th>
    </c:if>
   </c:forEach>
   </c:forEach>
  <display:column property="小计"
      title="小计"
      format="{0,number,####.##}" total="true"/>
 <display:footer>
 <tr>
  <td>总计</td>
  <c:forEach items="${model.YList.YColumn}" var="yColumn">
  <td><fmt:formatNumber value="${totalMap[yColumn]}"
        pattern="####.##"/>
  </td>
  </c:forEach>
 </tr>
 </display:footer>
</display:table>
 

另外一个例子:
<display:table name="${model.statResult}" class="simple"
 varTotals="totalMap">
 <display:column property="XMMC" title="项目名称" />
 <display:column property="KSRQ" title="开始日期" />
 <display:column property="FZRXM" title="负责人姓名" />
 <display:column property="GKDW" title="挂靠单位" />
 <display:column property="JFLY" title="经费来源" />
 <display:column property="XMZJF" title="项目总经费" total="true" />
 <display:column property="DNDKJF" title="当年到款经费" total="true"/>
 <display:footer>
  <tr><td colspan="5">总计</td>
   <td>
    <fmt:formatNumber value="${totalMap.column6}"
     pattern="####.####" />
   </td>
   <td>
    <fmt:formatNumber value="${totalMap.column7}"
     pattern="####.####" />
   </td>
  </tr>
 </display:footer>
</display:table>
总计某一列的方法:
${totalMap.column?}    ?为该列号
colspan="5":表示这个单元格占5个单元格
项目名称 开始日期  负责人姓名 挂靠单位 经费来源 项目总经费 当年到款经费
                                                   531      400
                                                   151       10
-------------------------------------
总计                              682     500 

posted @ 2007-05-25 10:42 LiuTing 阅读(748) | 评论 (0)编辑 收藏

DWRUtils API 使用方法

$("precloneNode1suf") 取得该对象;

DWRUtil.selectRange("selectRangeBasic", 5, 15) 选中selectRangeBasic文本框里面从第五个字符到第15个字符之间的字符.

DWRUtil._getSelection("selectRangeBasic") 得到selectRangeBasic文本框里选中的字符.

var arrayFive = [ 'One', 'Two', 'Three', 'Four', 'Five' ];
DWRUtil.addOptions('addOptionsBasic', arrayFive); 将数组添加到下拉菜单里面去;

DWRUtil.getValue('addOptionsBasic') 得到 addOptionsBasic 对象的值;
DWRUtil.getValue("precloneNode1Inner1suf", { textContent:true }); 后面加个参数,在 precloneNode1Inner1suf元素为"UL" 时,它返回了元素里面得值,也就是说去掉了HTML标签部分.

DWRUtil.getText('addOptionsBasic') 得到下拉框 addOptionsBasic 显示的文本;

var arrayObject = [
{ name:'One', value:'1' },
{ name:'Two', value:'2' },
{ name:'Three', value:'3' },
{ name:'Four', value:'4' },
{ name:'Five', value:'5' }
];
DWRUtil.addOptions('addOptionsObject1', arrayObject, "name"); 将数组添加到下拉菜单里面去;后面的参数是确定那个是给用户显示的文本,同时也是值;

DWRUtil.addOptions('addOptionsObject1', arrayObject, "name","value"); 同上,不过后面参数是: 3=文本;4=值;

var map = { one:1, two:2, three:3, four:4, five:5 };
DWRUtil.addOptions('addOptionsMap1', map); 同上, one 是值;1 是文本;
DWRUtil.addOptions('addOptionsMap1', map,true); 同上, 1 是值;one 是文本;

-------------------------------------------------------------------------------------
<ul id="removeItems">
<li>One</li><li>Two</li><li>Three</li><li>Four</li><li>Five</li>
</ul>

如果是列表显示,如上;则上面所有方法和select 下拉框使用一样;
-------------------------------------------------------------------------------------
DWRUtil.cloneNode('cloneNode1', { idPrefix:'pre', idSuffix:'suf' });克隆一个节点,参数一为要克隆的节点的id,第二个参数是在克隆的节点id前面加pre,后面加suf.(注意:如果该节点有子节点的话,子节点的名字也一样加)

DWRUtil.addRows(id, array, cellfuncs, [options]);
原理:

for each member in array
for each function in cellfuncs
create cell from cellfunc(array[i])
循环数组,循环函数,建立单元调用函数;(顺序决定)

例如:
DWRUtil.addRows('addRowsBasic', arrayFive, [
function(data) { return data; },
function(data) { return data.toUpperCase(); },
function(data) {
var input = document.createElement("input");
input.setAttribute("type", "button");
input.setAttribute("value", "DOM Test");
input.setAttribute("onclick", "alert('" + data + "');");
return input;
},
function(data) { return "<input type='button' value='innerHTML Test' onclick='alert(\"" + data + "\");'>"; }
]);

高级部分:
第四个参数为对单元的高级操作,主要下面的两个方法;
function defaultRowCreator(options) {
return document.createElement("tr");
};

function defaultCellCreator(options) {
return document.createElement("td");
};

例子:
DWRUtil.addRows( "demo2",[ 'Africa', 'America', 'Asia', 'Australasia', 'Europe' ] , cellFuncs, {
rowCreator:function(options) {
var row = document.createElement("tr");
var index = options.rowIndex * 50;
row.style.color = "rgb(" + index + ",0,0)";
return row;
},
cellCreator:function(options) {
var td = document.createElement("td");
var index = 255 - (options.rowIndex * 50);
td.style.backgroundColor = "rgb(" + index + ",255,255)";
td.style.fontWeight = "bold";
return td;
}
});

其中 options 参数的属性可用的为:(没试过,自己试试吧)

rowData: the element value from the array (the same for all cells in a row)
rowIndex: the key (if map) or index (if array) from the collection
rowNum: The row number counting from 0 in this section (so if you are using tbody, it counts rows in the tbody and not the whole table)
data: The 'computed' data value for the cell (cellCreators only)
cellNum: The cell number that we are altering counting from 0 (cellCreators only)


DWRUtil.setValues(); 批量设置值;
var settings = {
setValuesDiv:"setValuesDiv",
setValuesSpan:"setValuesSpan",
setValuesSelect:"two",
setValuesText:"setValuesText",
setValuesPassword:"AB",
setValuesTextarea:"setValuesTextarea",
setValuesButton1:"B1-Two",
setValuesButton2:"B2-Two",
setValuesRadio1:true,
setValuesRadio2:false,
setValuesRadio3:"one",
setValuesRadio4:"two",
setValuesCheckbox1:true,
setValuesCheckbox2:false
};
DWRUtil.setValues(settings);

DWRUtil.getValues(empty);批量获取值;
var empty = {
setValuesDiv:null,
setValuesSpan:null,
setValuesSelect:null,
setValuesText:null,
setValuesPassword:null,
setValuesTextarea:null,
setValuesButton1:null,
setValuesButton2:null,
setValuesRadio1:null,
setValuesRadio2:null,
setValuesRadio3:null,
setValuesRadio4:null,
setValuesCheckbox1:null,
setValuesCheckbox2:null
};
DWRUtil.getValues(empty);

DWRUtil.useLoadingMessage("Ping");//类似gmail那个样子,在右上角显示加载"ping";可用自定样式,具体查询;
http://getahead.ltd.uk/dwr/browser/util/useloadingmessage

DWRUtil.toDescriptiveString("id",数字);弹出调试信息,数字为0,1,2.一级比一级高.

DWRUtil.onReturn(event, submitFunction);一般在form表单里面,防止在文本框上按回车就提交表单.
例如:
<input type="text"
onkeypress="DWRUtil.onReturn(event, submitFunction)"/>
<input type="button" onclick="submitFunction()"/>

posted @ 2007-05-20 12:46 LiuTing 阅读(606) | 评论 (1)编辑 收藏

DWR框架实现Ajax

      Ajax是时下比较流行的一种web界面设计新思路,其核心思想是从浏览器获取XMLHttp对象与服务器端进行交互. DWR(Direct Web Remoting)就是实现了这种Ajax技术的一种web框架. 最近做的项目中我也将它用上了,感觉很是方便,比如动态生成javascript代码,隐藏的http协议,java代码和javascript交互的是javascript的对象(或字符串).  下面是我整理的文档.
  DWR主要由两部门组成。javascript与web服务器通信并更新web页;运行在web服务器的Servlet处理请求并把响应发回浏览器。

   1  .  配置web.xml
<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

  2    当我们想看dwr自动生成的测试页时,可在java代码
servlet中加
<init-param>
  <param-name>debug</param-name>
  <param-value>true</param-value>
</init-param>

这个参数DWR默认是false.如果选择true.我们可以通过url http://localhost:port/app/dwr ,你就可以看到你部署的每个DWR class。并且可以测试java代码的每个方法是否运行正常。为了安全考虑,在正式环境下你一定把这个参数设为false.

3  log信息配置

我喜欢用log4j输出日志,那么在log4j.properties下加,log4j.logger.uk.ltd.getahead.dwr = debug。这样可以看DWR的调试日志。

4  配置dwr.xml (和web.xml同目录)
 
<create creator="new" javascript="JDate">
<param name="class" value="java.util.Date"/>
</create>

这里的多数元素都是可选的 - 你真正必须知道的是指定一个creator和一个javascript名字。

creator属性 是必须的 - 它用来指定使用那种创造器。

默认情况下DWR1.1有8种创造器。它们是:

  • new: 用Java的new关键字创造对象。
  • none: 它不创建对象,看下面的原因。 (v1.1+)
  • scripted: 通过BSF使用脚本语言创建对象,例如BeanShell或Groovy。
  • spring: 通过Spring框架访问Bean。
  • jsf: 使用JSF的Bean。 (v1.1+)
  • struts: 使用Struts的FormBean。 (v1.1+)
  • pageflow: 访问Beehive或Weblogic的PageFlow。 (v1.1+)

javascript属性 用于指定浏览器中这个被创造出来的对象的名字。你不能使用Javascript的关键字。

scope属性 非常类似servlet规范中的scope。 它允许你指定这个bean在什么生命范围。选项有"application", "session", "request" 和"page"。这些值对于Servlet和JSP开发者来说应该相当熟悉了。

scope属性是可选的。默认是"page"。如果要使用"session"需要cookies。当前的DWR不支持ULR重写。

param元素 被用来指定创造器的其他参数,每种构造器各有不同。例如,"new"创造器需要知道要创建的对象类型是什么。每一个创造器的参数在各自的文档中能找到。请查看上面的链接。

include和exclude元素 允许创造器来限制类中方法的访问。一个创造器必须指定include列表或exclude列表之一。如果是include列表则暗示默认的访问策略是"拒绝";如果是exclude列表则暗示默认的访问策略是"允许"。


5 dwr.jar下载后放lib下

源码浅析

dwr的设计很象webwork2的设计,隐藏http协议,扩展性,兼容性及强。

通过研究uk.ltd.getahead.dwr.DWRServlet这个servlet来研究下dwr到底是如何工作的。

<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

这样/dwr/*下的所有的请求都是由这个servlet来处理,到底生理了什么呢,我们还是以例子来说明吧.

1 web服务器启动,DWRServlet init()方法调用,init主要做了以下工作。
设置日志级别、实例化DWR用到的单例类(这些类在jvm中只有一个实例对象)、读去配置文件(包括dwr.jar包中的dwr.xml,WEB-INF/dwr.xml. config*.xml)。
2 请求处理
DWRServlet.doGet, doPost方法都调用processor.handle(req, resp)方法处理。Processor对象在init()方法中已经初始化了。 

代码
public void handle(HttpServletRequest req, HttpServletResponse resp)  
        throws IOException  
    {  
        String pathinfo = req.getPathInfo();  
        if(pathinfo == null || pathinfo.length() == 0 || pathinfo.equals("/"))  
        {  
            resp.sendRedirect(req.getContextPath() + req.getServletPath() + '/' + "index.html");  
        } else 
        if(pathinfo != null && pathinfo.equalsIgnoreCase("/index.html"))  
        {  
            doIndex(req, resp);  
        } else 
        if(pathinfo != null && pathinfo.startsWith("/test/"))  
        {  
            doTest(req, resp);  
        } else 
        if(pathinfo != null && pathinfo.equalsIgnoreCase("/engine.js"))  
        {  
            doFile(resp, "engine.js", "text/javascript");  
        } else 
        if(pathinfo != null && pathinfo.equalsIgnoreCase("/util.js"))  
        {  
            doFile(resp, "util.js", "text/javascript");  
        } else 
        if(pathinfo != null && pathinfo.equalsIgnoreCase("/deprecated.js"))  
        {  
            doFile(resp, "deprecated.js", "text/javascript");  
        } else 
        if(pathinfo != null && pathinfo.startsWith("/interface/"))  
        {  
            doInterface(req, resp);  
        } else 
        if(pathinfo != null && pathinfo.startsWith("/exec"))  
        {  
            doExec(req, resp);  
        } else 
        {  
            log.warn("Page not found. In debug/test mode try viewing /[WEB-APP]/dwr/");  
            resp.sendError(404);  
        }  
    } 

dwr/*处理的请求也就这几种。

(1)dwr/index.html,dwr/test/这种只能在debug模式下使用,调试用。
dwr/engine.js,dwr/util.js,dwr/deprecated.js当这个请求到达,从dwr.jar包中读取文件流,响应回去。(重复请求有缓存)
(2)当dwr/interface/这种请求到来,(例如我们在index.html中的 <script type='text/javascript' src='dwr/interface/JDate.js'></script>)DWR做一件伟大的事。把我们在WEB-INF/dwr.xml中的
<create creator="new" javascript="JDate">
<param name="class" value="java.util.Date"/>
</create>
java.util.Date转化为javascript函数。
http://localhost:port/simpledwr/dwr/interface/JDate.js看看吧。
细节也比较简单,通过java反射,把方法都写成javascript特定的方法。(我觉得这些转换可以放到缓存里,下次调用没必要再生成一遍,不知道作者为什么没这样做)。
(3)dwr/exec
javascript调用方法时发送这种请求,可能是XMLHttpRequest或IFrame发送。
当然,javascript调用的方法签名与java代码一致,包括参数,还有javascript的回调方法也传到了服务器端,在服务器端很容易实现。回调方法的java的执行结果 返回类似 <script>callMethod(结果)<script>的javascript字符串,在浏览器执行。哈,一切就这么简单,巧妙。

我这里还有DWR中文文档. http://www.blogjava.net/Files/LiuTing/DWR中文文档.rar





posted @ 2007-05-20 12:45 LiuTing 阅读(4326) | 评论 (4)编辑 收藏

Acegi工作流程

任何一个安全系统都包括authentication和authorization两部分,Acegi相同。

第一部分:authentication

1、acegi通过AuthenticationProcessingFilter拦截login请求获取Principal和Credential信息(通俗一点就是用户名和密码);

2、验证用户名密码,由这个Filter调用认证管理器AuthenticatiomManager进行验证。

AuthenticatiomManager本身并不具备验证的功能,它相当与是一个验证控制器,由它来管理验证的过程及方式。AuthenticatiomManager是通过调用provider来进行验证的,一个manager中可以具有多个provider,但只要有一个provider验证通过,manager就认为验证成功。

这部分要明白三点:一,provider是可以配置进去的,因为acegi是基于spring的;二是AuthenticatiomManager是可以被重写的,你可以将manager改成你自己希望的控制器;三,好好利用event,这是标准的observer模式。acegi中的设计模式研究将在以后的贴子中讨论。

3、provider进行验证。

provider是真正的验证模块,并且决定了验证的模式。provider目前acegi提供了dao、jaas,cas,x509,ldap等几种验证方式,这些验证方式的具体内容可以查阅acegi的文档。provider验证通过后将Authentication对象返回。

4、AuthenticationProcessingFilter将对象保存到ContextHolder中。Authentication部分结束。

第二部分:authorization

1、用户提交请求,拦截器FilterSecurityInterceptor拦截请求,拦截器是一个Filter.

2 、鉴权,拦截器调用AccessDecisionManager进行鉴权。

AccessDecisionManager是通过投票的方式来决定是否有权限访问资源。所谓投票就要包括投票的参与者和投票的策略。

投票的参与者decisionVoters,这是AccessDecisionManager的一个属性。decisionVoter能从某一个角度决定用户是否能访问资源,例如RoleVoter来判断用户的角色是否有权限访问资源,MaxuserVoter来决定某个资源的访问用户数是否已经达到了最大值等。

投票策略。投票的策略是通过不同的AccessDecisionManager来实现的,例如acegi提供的AffirmativeBased对象,这个对象的策略就是只要有一个投票通过就全体通过。UnanimousBased对象的策略是必须全体投票通过才能通过。但在大多数情况下acegi提供的AccessDecisionManager不能满足我们的要求,这就需要我们去实现AccessDecisionManager接口,去定制适合自己项目的策略。

3、投票。投票对象必须实现AccessDecisionVoter接口。投票对象关注的是某一方面的决定权,如果投票通过则Vote方法来完成的。vote方法必须返回一个int型的数据代表投票结果,它们是AccessDecisionVoter的三个静态成员属性:ACCESS_ABSTAIN,,ACCESS_DENIED和ACCESS_GRANTED,它们分别是弃权,否决和赞成。
 

安全拦截器

  拦截器如何工作
  MethodInvocation拦截器
  FilterInvocation拦截器

认证

  认证请求
  认证管理器
  Authentication Provider

授权

  Access Decision Manager
  Voting Decision Manager
  授权管理推荐

ContextHolder的用户接口

  用户接口目标
  HTTP会话认证
  HTTP Basic认证

posted @ 2007-05-20 12:44 LiuTing 阅读(639) | 评论 (1)编辑 收藏

使用Acegi时获取用户信息的几个函数

1 /**
 2    * 取得当前用户名
 3    *  @return
 4     */

 5    public   static  String getUsername() {
 6     Context context = ContextHolder.getContext();
 7      if (context != null ) {
 8        if (context  instanceof  SecureContext) {
 9         SecureContext sc = (SecureContext)context;
10         Authentication auth = sc.getAuthentication();
11          if (auth != null ) {
12           Object principal = auth.getPrincipal();
13            if (principal  instanceof  UserDetails)  {
14              return  ((UserDetails)principal).getUsername();
15           }
else {
16              return  principal.toString();
17           }

18         }

19       }

20     }

21      return   null ;
22   }

23    /**
24    * 取得当前用户密码
25    *  @return
26     */

27    public   static  String getPassword() {
28     Context context = ContextHolder.getContext();
29      if (context != null ) {
30        if (context  instanceof  SecureContext) {
31         SecureContext sc = (SecureContext)context;
32         Authentication auth = sc.getAuthentication();
33          if (auth != null ) {
34           Object principal = auth.getPrincipal();
35            if (principal  instanceof  UserDetails)  {
36              return  ((UserDetails)principal).getPassword();
37           }
else {
38              return   null ;
39           }

40         }

41       }

42     }

43      return   null ;
44   }

45    /**   */ /**
46    * 取得当前用户session id
47    *  @return  sessionid or null
48     */

49    public   static  String getSessionID() {
50     Context context = ContextHolder.getContext();
51      if (context != null ) {
52        if (context  instanceof  SecureContext) {
53         SecureContext sc = (SecureContext)context;
54         Authentication auth = sc.getAuthentication();
55          if (auth != null ) {
56           Object details = auth.getDetails();
57            if (details  instanceof  WebAuthenticationDetails)  {
58              return  ((WebAuthenticationDetails)details).getSessionId();
59           }
else {
60              return   null ;
61           }

62         }

63       }

64     }

65      return   null ;
66   }

posted @ 2007-05-20 12:43 LiuTing 阅读(358) | 评论 (0)编辑 收藏

Acegi使用2

流程说完了,接下对上面提到的问题解释一下:

引用

先使用URL拦截的方式去对付大多数的情况,然后少数URL搞不定的再加一个接口,让Action自己去判别。这样用两个拦截器对Action进行拦截,如果Action实现了hasPermission接口,那么就交给Action自己判断,如果Action没有该接口,就查找内部的URL和权限的对照表来判断。

 

这个情况比较复杂,因为是要对Action进行调用,所以要看使用的web框架来定夺。是用不同Web框架要是用不同的Web框架拦截器。所以恐怕acegi力不从心,挺多在filter里面配置一下是否要是用Action自身校验。但是想法很好,赞。

 

引用

你怎么把信息有效的传递给Web层和其他的Facade层进行用户友好性的提示处理?

 

acegi校验失败的时候会抛出AuthenticationException异常,然后放在session里面,
在错误转向页面可以这样是用

代码
<%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>    
<%= session.getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY) %>    
<%= session.getAttribute(SecurityEnforcementFilter.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY) %> 

不过这么用实在太难看了,晕了。可以自己用tag来搞定

 

 

引用

很多系统需要根据权限不同,生成不同的UI组件(例如管理员的菜单和普通用户菜单就肯定不一样),这也需要在Web层的View进行控制。

acegi对View的处理就是使用tag,原来的acegi好像没有什么tag,简直是烂,现在有了tag,说一下是用的方法,但是说实话他的tag实在是不够强。

 

老版的web.xml

代码
1<taglib>   
2  <taglib-uri>http://acegisecurity.sf.net/authz</taglib-uri>   
3  <taglib-location>/WEB-INF/authz.tld</taglib-location>   
4</taglib>  

在页面中使用
代码
1<authz:authorize ifAllGranted="ROLE_SUPERVISOR">   
2<td>   
3<HREF="del.htm?id=<c:out value="/${contact.id}"/>">Del</A>   
4</td>   
5</authz:authorize>  

ifAllGranted是说所有的权限都有,用','分割权限
可以替换成ifAnyGranted: ifNotGranted:

 

 

代码
1<authz:authentication operation="username"/>   

 

这个是用来显示你的权限信息的。

 

代码
1<authz:acl domainObject="${contact}" hasPermission="16,1">   
2<td><HREF="<c:url value="del.htm"><c:param name="contactId"   
3value="${contact.id}"/></c:url>">Del</A></td>   
4</authz:acl>  

posted @ 2007-05-20 12:42 LiuTing 阅读(484) | 评论 (0)编辑 收藏

Acegi使用1

首先要配置一个filter,这个filter用一个代理bean写在了spring里面,其实根正常的filter没有任何区别。

代码
1<bean id="securityEnforcementFilter"   
2    class="org.acegisecurity.intercept.web.SecurityEnforcementFilter">   
3    <property name="filterSecurityInterceptor">   
4        <ref local="filterInvocationInterceptor" />   
5    </property>   
6    <property name="authenticationEntryPoint">   
7        <ref local="authenticationProcessingFilterEntryPoint" />   
8    </property>   
9</bean>  

 

"filterInvocationInterceptor" 是一个拦截器,说是拦截器,其实就是在filter里面执行一下他的拦截方法,这里可没有什么aop.
authenticationEntryPoint 交验失败的时候转到的地方,为什么说是地方,因为通过配置可以转到其它的url甚至其它的协议下(http 转到 https等等)
 
代码
 1<bean id="authenticationProcessingFilterEntryPoint"   
 2    class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">   
 3    <property name="loginFormUrl">   
 4        <value>/error.security</value>   
 5    </property>   
 6    <property name="forceHttps">   
 7        <value>false</value>   
 8    </property>   
 9</bean>  
10

 

这个就是失败的时候转到的地方,我们可以配置url和是否使用https

 

代码
 1<bean id="filterInvocationInterceptor"   
 2    class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">   
 3    <property name="authenticationManager">   
 4        <ref bean="authenticationManager" />   
 5    </property>   
 6    <property name="accessDecisionManager">   
 7        <ref local="httpRequestAccessDecisionManager" />   
 8    </property>   
 9    <property name="objectDefinitionSource">   
10        <value>   
11            CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON    
12            PATTERN_TYPE_APACHE_ANT    
13            /index.jsp=ROLE_ANONYMOUS,ROLE_USER    
14            /j_acegi_switch_user=ROLE_SUPERVISOR   
15            /login.security=ROLE_ANONYMOUS,ROLE_USER                /test.do=ROLE_CODER   
16            /**.do*=ROLE_USER    
17        </value>   
18    </property>   
19</bean>  

 

这个就是前面提到的拦截器。简单解释一下:
authenticationManager 在acegi里面的主要作用就是管理维护用户的权限角色等信息,比方说想要用户的ROLE就要在这里面拿了。里面配置了多种全县的来源,可以从DAO里面来(就是数据库里面),可以是cookies里面的,也可以是匿名的权限,每种权限都以一种Provider的形式提供:

代码
 1<bean id="authenticationManager"   
 2    class="org.acegisecurity.providers.ProviderManager">   
 3    <property name="providers">   
 4        <list>   
 5            <ref local="daoAuthenticationProvider" />   
 6            <ref local="anonymousAuthenticationProvider" />   
 7            <ref local="rememberMeAuthenticationProvider" />   
 8        </list>   
 9    </property>   
10</bean>

 

objectDefinitionSource在acegi里面就是配置权限信息,说明哪一个url需要什么权限才能访问,acegi默认用<value>来表示,其实这正是acegi的不足之处,还好能够补救。我来说明一下:
我们知道在spring里面<value>标签比较特殊,spring首先找到这个属性的类型,然后把value里面的内容以String的类型取出来(Spring做了一下包装,为TypedString)。然后根据这个属性的类型找他的Editer,然后用Editer来处理String为需要的类型。但是我们不希望用String来表达url,很明显url里面有=就不会玩了。我们可以把这个信息写到数据库里面,然后读取,这里面不说了以前有一位高手已经解释过了。

接下来就是httpRequestAccessDecisionManager了,AccessDecisionManager在acegi里面是决策者,就是根据你所拥有的权限和访问URL需要的权限来决定你到底能不能访问。

 

代码
 1<bean id="httpRequestAccessDecisionManager"   
 2    class="org.acegisecurity.vote.AffirmativeBased">   
 3    <property name="allowIfAllAbstainDecisions">   
 4        <value>false</value>   
 5    </property>   
 6    <property name="decisionVoters">   
 7        <list>   
 8            <ref bean="roleVoter" />   
 9        </list>   
10    </property>   
11</bean>  

 

决策者里面是投票者,这个上面已经解释过了,一个投票者校验一种权限。整个流程已经说完了。

posted @ 2007-05-20 12:41 LiuTing 阅读(393) | 评论 (0)编辑 收藏

Eclipse报表插件birt

 

1. BIRT 概述

BIRT是一个Eclipse-based开放源代码报表系统。它主要是用在基于JavaJ2EEWeb应用程序上。BIRT主要由两部分组成:一个是基于Eclipse的报表设计和一个可以加到你应用服务的运行期组件。BIRT同时也提供一个图形报表制作引擎。

    使用BIRT,能制作出多样化的报表到你的应用中。比如:列表 lists ,图表 charts,混合报表 Compound Reports,交叉表(二维表)Crosstabs(二维报表目前在BIRT 2.1.1 还未实现,但在以后的版本中将会实现此功能)。

2. BIRT的安装

2.1>birt的安装

Eclipse 3.1
支持 BIRT 2.0

Eclipse 3.2 支持 BIRT 2.1

 

目前我使用的版本是BIRT 2.1.1BIRT的安装有很多的方法。

方法一:

Eclipse的官方网站http://download.eclipse.org/birt/downloads去下载 BIRT 插件(birt-report-designer-all-in-one-2_1_1),解压后,它就是一个拥有 BIRT插件的Eclipse。如果你已经安装了Eclipse,那么把下载的文件夹的内容拷到所对应的 Eclipse 的文件夹里(pluginsfeatures两个目录),之后的第一次启动是通过DOS进入到Eclipse目录,启动 Eclipse -clean 命令,此命令是清除 Eclipse 缓存里的记录并启动Eclipse。这也是最简单的安装BIRT方法。

 

方法二:

    BIRT 2.1开始,可以在 Eclipse 里直接下载。[帮助]à[软件更新]à[查找并安装]

 

方法三:

    如果你的 Eclipse 已经安装或者不想使用方法一安装,可以进行分别安装的方法。EMF, GEF BIRT 都是需要通过 Eclipse download page下载的。请注意以下的配置要求:

  • BIRT 2.0.2
  • Eclipse 3.1
  • GEF 3.1.
  • EMF 2.1.
  • JRE 1.4.2/JRE 1.5.
  • BIRT 2.1/2.1.1
  • Eclipse 3.2.
  • GEF 3.2.
  • EMF 2.2.
  • JRE 1.4.2/JRE 1.5.
  • BIRT 2.2M4 and higher
  • Eclipse 3.3.
  • GEF 3.3.
  • EMF 2.3.
  • JRE 1.5.

GEF:图形编辑框架,是一个通过 BIRT UI 使用的Eclipse 插件

EMF:用来制作图表的框架

具体的安装和配置请参考 Eclipse官方网站 这里我还推荐一个BIRT 2.0.1的安装视频 希望对大家有所帮助。

注意:iTextAsian.jar 是为了支持用PDF输出时亚洲人的字体。prototype.js 是安装 BIRT 2.0.2 或者更早的版本使用的,目前版本不需要了。

提示:我个人使用的是方法一,首先安装比较方便,其次如果使用方法二,在Eclipse [帮助]à[帮助内容] 里没有关于 BIRT 的帮助文档,这样对学习不是很方便。

 2.2    birt版本升级

1.删除在 plugins 目录下的所有以 org.eclipse.birt 开头的文件。

2.
按照上面的方法一进行安装。

3.重新启动 Eclipse -clean

 2.3  语言包安装

    目前 BIRT2.1.1 已经有中文语言包。下载完毕以后把对应的目录 copy 到对应的 Eclipse 目录下,然后 Eclipse -clean 启动。

  

3. BIRT应用

    在这里我提供了一个 “BIRT中文教程”,在Eclipse官方网站也有它的演示视频,Customers.rptdesign 是做好的报表。

 

JDBC Drivers

    以上例子介绍的是使用“示例数据库”的数据源,在大多数情况下我们使用的是自己的JDBC driver,下面介绍连接 Oracle 数据库。

    打开 Eclipse 后进入 [窗口]à[打开透视图]à[报表设计],新建项目 [文件]à[新建]à[项目],选择“业务智能和报表工具”à“报表项目”,起项目名。新建的项目上,右键 新建报表,给报表起名字,报表的后缀名是 .rptdesign

    [数据源]à[新建数据源],选择“JDBC数据源”(别忘了给数据源起名字),“管理驱动程序”à“添加”,把所需要的数据库 jar 包添加进来。选择oracle.jdbc.driver.OracleDriver ( v1.0),写上正确的数据库 URL,用户名,密码,点击“测试连接”,显示 连接成功。

 

报表的种类有很多,具体的制作过程参考[帮助]à[帮助内容]

 

提示:交叉表即二维报表在目前BIRT 2.1.1 还未实现,但在以后的版本中正在计划实现此功能。

 

4. BIRT部署

BIRT 2.1.1所对应使用的部署工具是birt-runtime-2_1_1,可以到 Eclipse 官方网站下载。

 
4.1. tomcat 部署

我使用的版本是 tomcat 5.5,步骤如下:

1)      mail.jar activation.jar拷到Tomcat 5.5\common\lib

2)      iTextAsian.jar itext-1.3.jar拷到

WebViewerExample\WEB-INF\platform\plugins\com.lowagie.itext\lib

3)      连接数据库的包拷到

WebViewerExample\WEB-INF\platform\plugins\org.eclipse.birt.report.data.oda.jdbc_2.1.1.v20060922-1058\drivers

4)      WebViewerExample整个目录copy Tomcat 5.5\webapps,可以更改文件夹名。

5)      完成的报表可以直接复制到根目录下,也可以新建文件夹目录。

 

现在BIRT支持两种浏览报表模式,frameset是支持显示分页的,采用Ajax技术。run暂不支持显示分页,直接输出HTML流。比较一下它们的区别,如下:

http://localhost:8080/WebViewerExample/frameset?__report=Customers.rptdesign

http://localhost:8080/WebViewerExample/run?__report=Customers.rptdesign

注意:

a)      问号后面的是双下划线

b)      以上报表是放在根目录下,如果是放在自己新建的目录比如 myreport URL应是:

http://localhost:8080/WebViewerExample/run?__report=myreport/Customers.rptdesign

 
4.2. Weblogic Server 部署

我使用的版本是 Weblogic Server 9.2,它是 BEA WebLogic 产品的一部分。BEA WebLogic Server 是一个功能丰富、基于标准的现成的企业 J2EE应用服务器。

Weblogic Server 有很多的域组成,medrec wl_server 是其中自带的域,也可以自己创建新的域。域中可以包含许多服务器,在服务器中可以部署许多的应用程序。域有一个默认的服务器,它是管理服务器。每个域中的管理服务器是唯一的,其他创建的服务器叫受管服务器。

Weblogic Server 的安装就不多做介绍了,主要介绍它的配置和部署。下面介绍一下新建域和部署的步骤:

1)      运行开始 -> 程序 -> BEA Products -> Tools -> Configuration Wizard

2)      选择 Create a new WebLogic domainNext

3)      Select Domain Source 界面保持默认,Next

4)      User name 处输入用户名,如 userpassword 输入密码,如12345678(密码要求 8 位),Next

5)      Configure Server Start Mode and JDK 界面中保持默认,即 JDK 选择的是“Sun SDK 1.5.0_04 @ C:\BEA\jdk150_04,注意这里若选择“Other JDK”配置比 SDK 1.5.0_04 更高的版本,服务器启动时将会出现异常,所以最好保持默认,然后 Next

6)      Customize Environment and Services Settings 界面中默认为 NoNext

7)      Domain name 处输入 mydomain,点击 Create

8)      完成后点击 Done 关闭 Configuration Wizard 对话框。

9)      运行开始 -> 程序 -> BEA Products -> User Projects -> mydomain -> Start Admin Server for Weblogic Server Domain,启动创建域的服务器。如果最后显示 <Server started in RUNNING mode> 则说明服务器启动成功。

10) 在地址栏输入 http://localhost:7001/console 进入管理控制台,输入刚才的用户名和密码。点击 更改中心 的[锁定并编辑],域结构 中的[部署],点击[安装]。找到你要部署的应用程序,之后都为默认,最后点完成。在更改页面上点击[保存]完成所需的更改后,单击 更改中心 中的[激活更改]注意:只有点击了[激活更改]后才真正保存了更改。某些在管理控制台中所做的更改在激活后立即生效。有些更改需要重新启动此更改影响到的服务器或模块。后面的这些更改称为非动态更改。非动态更改在管理控制台中由此警告图标表示: 如果对非动态配置设置进行了编辑,则只有在重新启动服务器之后,所有对动态配置设置所进行的编辑才会生效。

11) 点击 域结构 中的[部署],启动刚才部署的应用程序。

12) 在地址栏中输入 http://localhost:7001/部署程序名在web中显示应用程序。

 

    BIRT的部署方法基本和上面步骤相同,注意一点,在部署前将

commons-logging.jar 拷入 WebViewerExample 中的 lib 包中,否则最后显示会报错。

 

    Weblogic Server 的部署方法有很多,weblogic.Deployer、管理控制台、WLST 等等。刚才介绍的管理控制台的部署,它是一个可视化的界面,用起来比较方便。

 

推荐:在这里推荐使用一种应用程序的目录管理方法,新建目录结构如下:

C:myApplication

app

plan

将你的应用程序放入此目录

部署完后将会再此目录下自动产生plan.xml文件

用此目录有利于很好的管理配置文件。

 

关于 Weblogic 的更多详细资料,我这里推荐几个比较好的官方学习网站:

www.bea.com.cn          bea的产品网站

edocs.bea.com.cn       bea的比较系统的学习网站

dev2dev.bea.com.cn      一般介绍bea的最新技术,动态等的网站
 
4.3. web外观显示

    部署好的报表页面显示样式是固定的,在多数情况下我们都希望修改外观,下面将对此做简单介绍。

Webcontent是主要文件夹:

1)      Webcontent中的ajax目录,完成了在web上显示的报表分页的功能。

2)      Styles目录中的style.css是整个报表在web上显示的外观,包括工具条颜色,字体。文件注释很清楚,具体修改可以参见注释。

3)      pages目录pages/control/ NavigationbarFragment.jsp 分页那一条工具栏的 JSP pages/control/ ToolbarFragment.jsp 工具按纽JSP,可以修改以保留自己想要使用的功能按纽。pages/control/ ProgressBarFragment.jsp 它是显示刚开始读取报表时,显示条的样式。

4)      Layout目录 FramesetFragment.jsp 可以修改报表标题。

 

WEB-INF/web.xml文件中,BIRT_VIEW_REPORT_ROOT 是配置报表路径的属性,如果没有配置则是默认路径:$tomcat$\webapps\birt

    目前在web上显示的所有的都是英文。项目有一个资源文件,打包在lib/viewservlets.jar中,…/…/resource/messages.properties文件中。显示中文的解决方法是,用unicode转换器把要显示的中文事先转换好,然后替换资源文件中的英文。

 

5. BIRT总结

BIRT是目前比较常用也是比较方便,效率较高的报表制作工具。当然其发展时间不长,所以还有很多的缺陷和未完善的地方,之后的版本也在不断改进,因此我也将会一直跟踪它的技术发展,不断更新我的文档。

posted @ 2007-05-20 12:40 LiuTing 阅读(4576) | 评论 (1)编辑 收藏