2009年8月31日星期一

product,content和item

今天想弄清楚产品(product),内容(content)和item(item又分电信定义的item和高通定义的item)之间的关系。到现在对这些还不是很明了,主要是因为没有和实际联系起来,光看代码和配置文件只能了解皮毛。

例如,我知道product下可以包含多个content,每个content也可以包含多个item(电信定义的item),代码也是这样操作解析的,但是这三者到底对应现实中的什么?比如,一款游戏:魂斗罗,它到底算哪样?是product?content?还是item呢,这个到现在还是浆糊。这不弄清楚,对整个代码和系统就不能完全把握。

所以今天从电信的文档和配置文件开始。我先看了productinfo配置文件,和电信文档配合一起看,productinfo配置文件内容很简单,基本上没有什么有效信息,需要关注的只有这几个tag:productID,chargingPolicyID。还有一个serviceID不知道有什么作用。productID是该product的标识,很重要,很多地方都要用到。chargingPolicyID,计费策略,这是电信方面的概念,电信叫charging policy,高通那边叫price plan,本质上是一样的,名称不同而已,当然形式和操作上也有些区别,这个在电信文档的附录“附录 D: BREW 产品计费策略与 ISMP 内部处理关系”有详细的说明。chargingPolicyID标识一个charging policy,也是很重要的,有一个配置文件charginfo.xml,productinfo和charginfo之间要建立联系,就要通过这个 chargingPolicyID。

除了这两个tag,其它的基本上可以忽略。productinfo文件内容相对简单。而上面提到了 charginfo文件,其实charginfo和厘清product,content和item之间关系这个主题的关系不大,这里只是简单介绍一下。产品计费策略信息文件的内容,比如productinfo要稍多,但是值得关注的tag也不多:chargingPolicyID,baseFee,FeeType。chargingPolicyID不用说,FeeType很重要,在代码中就是根据它来解析的。

下面看内容描述文件,这个文件里包含了有关内容的信息。内容描述文件可以包含多个内容,每个内容的tag是“contentInfo”,在每个contentInfo下又包括几个很重要的tag:contentID,itemID,contentURL,modelList和itemBREWProperty,contentID 和itemID不用说,是content和item的唯一标识。从内容描述文件可以看出,一个content是可以包含多个item的。在 contentInfo这个tag下,可以关注一个问题,就是所有与item相关的信息都是放在“Item”这个tag下的,每个contentInfo 可以包含多个Item,标识每个content可以包含多个item,那么content和item到底对应现实中的什么呢?

从电信文档里对Item的解释可以看出端倪,文档里是这么解释的“ 同一content下的不同Item提供针对同一内容的不同终端类型适配的能
力” 从这段解释可以看出:item是针对于不同终端的。而从各自的配置文件内容,也可以印证。首先看productInfo,这个配置文件里面包含了 chargingPolicyID,说明product这个概念,是针对于收费策略的,也可以这样理解,有着相同收费策略的应用,我们都可以把它们看作是同一产品。

而对于content,它比product要小,他和收费策略没有直接关系,可以把它看作某个现实的应用,例如游戏魂斗罗。它可以看作一个content,但是item又是什么,更加电信文档里的解释,item是针对某个或者是某类终端的content,例如魂斗罗是一个 content,但是对于两个手机,姑且称为手机1和手机2,手机1和手机2的硬件不同,例如屏幕大小等等不一样,语言的选择不一样(一个使用英文,一个使用中文)等等不同。对于同一个内容:魂斗罗,不能在两个手机上都适用,所以针对手机1,有一个适用的应用,姑且称为魂斗罗1,这个就是item,而对手机2,同样有个魂斗罗2,这也是个item。所以魂斗罗这个content下面根据不同的手机终端,就有了两个item:魂斗罗1和魂斗罗2。这两个 item是属于魂斗罗这个content的。

在内容描述文件中,itemInfo这个tag下包含的东西值得关注,首先是itemID,这没什么好说的,然后是contentURL,这个很重要,它表示内容实体文件在远程FTP上的存放路径。另一个值得关注的是modelList标签,它表示这个item支持哪几款终端,例如W219,D06,C510,表示这个 item支持的终端有三种:W219,D06和C510。从这里可以看出,item和终端是紧密联系的。

到此,可以这样说,product,content和item,它们的作用范围逐渐减小,逐渐具体。它们的侧重点也是不一样的,product的侧重点是计费,content的侧重点是应用,item的侧重点是适用的终端类型。现举下面的一个例子来说明它们的关系:
有一个product,包括两个内容:魂斗罗和三国志。它们的计费策略都是一样,都是包月10块。如果还有一款应用也使用这个计费策略,也可以加入到这个产品中来。对于魂斗罗这个content,针对iphone,Gphone和Nokia N95,这三款终端,又有了三个item,分别是魂斗罗1,魂斗罗2,魂斗罗3。这三个item分别使用于三款手机,某个item不能被安装到其它两个终端上。

电信的系统是以product为中心的,围绕着product。而高通的系统,没有product,是围绕着item的。对于高通的item,现在还不了解,应该和电信的item有很大的相似处。但是要弄清楚的是,高通的计费策略是不是针对item的,或者说是于计费策略是和 item绑定的。因为电信的计费策略(charge info)是和product绑定的。

2009年8月14日星期五

PostActivation,price handle的获取

对于PostActivation,PostActivationServlet类的doGet方法到没多少内容,它调用PostActivationHandler.fetchItemDetails方法,这个方法返回OPCacheVO对象,查看OPCacheVO的内容,发现有不少东西,productID,contentID,四元组,price handle,还有语言信息,几乎包含了所有必要的信息。随后doGet调用fetchProductIDContentID方法将这些信息都保存到数据库中。

PostActivationServlet的init方法里初始化了两个job:PostActivationJob和NewDistributionJob。

PostActivationJob 是调用PostActivationHandler的invokeProcess方法,这个方法的功能是“This method is used for getting Pricehandle from BrewZone webservice and updating into database table, for generating Feedback file and ftping it to FTPSubsystem.”。貌似这个方法把所有的事情都做了,从BrewZone取得price handle,更新数据库,产生回执文件。

但是粗看了PostActivationHandler的invokeProcess方法,感觉里面用到的很多数据都是已经保存在数据库中了,所以我感觉应该是另一个job,也就是NewDistributionJob执行过后,得到并保存了数据,PostActivationJob才可以顺序地进行,所以决定先看NewDistributionJob。

NewDistributionJob调用了 NewDistributionHandler.invokeProcess方法,这个方法的功能是“This method is used for getting catalog info from BrewZone webservice and updating into database table”,也就是它从BrewZone获得数据并保存到数据库中。invokeProcess调用BREWZoneBase.getInfo方法,它的返回值是InfoVO对象,InfoVO类的成员很简单,值得关注的一个成员是catalogId。得到的InfoVO中的信息又被保存到 CatalogInfoVO对象中。CatalogInfoVO中也有catalogId和其它信息。最后把信息保存到数据库中。

回到 PostActivationHandler.invokeProcess方法,它回频繁地调用getCatalogInfo获得catalog的信息(主要是catalogId),这些信息就是NewDistributionHandler.invokeProcess保存到数据库中的。 invokeProcess调用getPriceHandles获得price handle,但是这个方法没看太懂,它的参数是url和catalogId,很明显,就是到url的地址去获取符合catalogId的price handle。但是细看这个方法的实现,发现好像没做什么,它先是调用brewZoneClient.getTopLevelCategories去获得List,但是奇怪的是虽然调用了getTopLevelCategories,但它的返回值并没有被保存到一个List对象中,也就是说这里调用getTopLevelCategories貌似没起作用。然后有调用OPCacheDAO.opCacheReload,这个方法也奇怪,就在数据库中设置了一下时间戳而已。

随后PostActivationHandler.invokeProcess调用getFeedBackInformation,它返回ProductsVO对象,ProductsVO类有必要关注一下它的成员:
ProductsVO { List product; }

ProductVO { String productId; List content; }

ContentVO { private String contentId; private List ItemDetail; }

ItemDetailVO { private String itemURL; private int opFlag; private String itemId; }

从这些类包含的成员,再对照PriceHandle_2009042900000001.xml文件,就可以发现,这些类成员和pricehandle的xml文件的tag是一一对应的。

当 getFeedBackInformation方法返回ProductsVO对象后,pricehandle的xml文档的内容基本上就确定了。随后 uploadFeedBackFile方法被调用生成feedback file,也就是PriceHandle_2009042900000001.xml文件。

就目前来看,对语言信息来说,虽然语言信息保存到了OPCatchVO中了,但是在代码里貌似并没有使用这个OPCatchVO,而是用ProductsVO来构建了price handle xml文件,xml文件里也没有包含语言信息。

2009年8月13日星期四

CMSEntity之九,提交item的逻辑

接昨日:
获取内容描述文件并对其进行初步分析后,ContentProcessorJob会调用ContentHandler类的方法做进一步处理。方法invokeProcess是入口,invokeProcess首先就调用getSuitableBatch去获得合适的batch,所谓合适的 batch,就是在上篇博客中提到的,处理完所有的item后,要将其状态更新,置为saved,以便于后面的操作。所以 getSuitableBatch就是将这些状态为saved的项取出来。

getSuitableBatch得到的是 List,之后invokeProcess调用commonStorageHandling来“handle the common storage”(不太明白这里的意思),昨天提到,可以通过batchId来得到content的信息,commonStorageHandling调用getContentByBatchID返回List。commonStorageHandling后面的处理让我有些摸不着头脑,它先是检查每个ContentItemVO的ProcStatCodeID,如果是CONTENT_STATUS_SAVED或者CONTENT_STATUS_TIMEDOUT,再去获得ISMPItemVO,也去挨个检查ProcStatCodeID,如果是 ITEM_FETCH_SUCCESS或者ITEM_STATUS_TIMEDOUT,然后到本地目录去获取内容实体zip文件,如果文件不存在,将 ISMPItemVO的ProcStatCodeID改为ITEM_STATUS_PARSED。这一段操作的目的还不清楚。 commonStorageHandling的返回值是boolean型,表示内容实体zip文件在本地目录是否存在。

commonStorageHandling返回到invokeProcess,invokeProcess根据返回值,如果为真,它调用方法processBatchNotify。

processBatchNotify也是首先用getContentByBatchID获得List。然后对每个ContentItemVO调用方法processContent,这个方法非常重要:“Iterate the item list of this content, prepare the MetaFile and generate the zip, Invoke the BMC web service to Submit the item. Use the return brew_item_id to update ContentItemVO object. update the ItemVO's zip file directory and metafile XML file and final zip file result.”。也就是说,这个方法生成了Meta文件,并将Meta文件和内容实体zip文件一起再生成一个zip文件,并将这个zip文件传给 BMC。

processContent是根据OPFLAG进行操作的,有三种OPFLAG:OPFLAG_ADD,OPFLAG_MODIFY和OPFLAG_DELETE。

先看OPFLAG_ADD的情况:
1. 查询数据库,用方法checkContentItemExists,这个方法的功能是“This is to check content is exist with current content id and partNo in BSG”。也就是查找在当前数据库中是否已经存在该内容。注意这里使用的参数是content ID,manufacturer partner no和content no,前面两个很好理解,最后一个content number不知道是什么。根据这三个来判断该内容在当前库中是否存在。如果存在,进入2.x,否则进入3.x。

2.1 调用方法checkItemExistsForTheContent,这个方法的功能是:“This is to check content is exist with current content id and partNo in BSG”。这个解释让人迷惑,为什么又去判断content是否存在?所以我判断这里的注释肯定不对,印度兄弟偷懒了,把 checkContentItemExists的注释又拷贝到这里。按checkItemExistsForTheContent这个方法名来判断,不好说这个方法是什么功能,它的返回值如为true,进入2.2,否则进入2.3。
2.2 用方法getItemVOList去获得List,然后对每个ISMPItemVO调用checkISMPItemExists,这个方法的功能是“This is to verify that Item is already exist or not”。返回true表示content里已经存在该item了,进入 2.2.a,否则进入2.2.b。
2.2.a 调用方法applyPlatformLanguage,这个方法首先调用ChangeListCreator.getUseableChangeList,现在不知道change list是干啥用的,它返回ChangeListVO,如果不为空,则调用 ItemSubmissionHandler.addOprItemPlatform方法,这个方法很长,这里先不细谈,它的功能是把该item的平台信息(支持的手机类型)保存。
2.2.b 调用方法processAsUpgradeItem,将该item作为新的item上传给BMC。这是通过ItemSubmissionHandler.processItem方法实现的。
2.3 用方法getItemVOList去获得List,对每个ISMPItemVO,调用ItemSubmissionHandler.processItem方法将其作为新的item提交到BMC。

3.1 当前的content不存在,首先调用checkContentExists,这个方法的功能是“This method checks whether content exists or not”。这里让人很迷惑,前面不是已经用checkContentItemExists检查过content是否存在了吗,为什么又检查一次?如果返回true,就调用markedForDelete,这个方法“This method is used for delete handling when only contentId exists”。
3.2 用getItemVOList得到List,对每个ISMPItemVO,调用checkItemExists,这个方法又搞不清楚,它的注释是“This is to check the Item exists or not”。迷惑的是:这里说的存在到底是存在于哪里?是存在于BSG的数据库中?还是存在于content中还是哪里?如果存在,进入3.3.a,否则进入3.3.b
3.3.a 用方法setProcStatCodeID将item的状态置为ITEM_STATUS_FAILED,表示item已存在,然后调用updateItemStatusNLoc更新状态。
3.3.b 调用ItemSubmissionHandler.processItem方法将其作为新的item提交到BMC。

OPFLAG_MODIFY的处理:
1. 调用modifyHandling方法,用方法getItemVOList去获得List,然后对每个ISMPItemVO调用checkISMPItemExists,这个方法的功能是“This is to verify that Item is already exist or not”。返回true表示content里已经存在该item了,进入2.1.a,否则进入2.1.b。
2.1.a 用ChangeListCreator.getUseableChangeList()去获取change list,如果成功,用addOprItemPlatform把该item的平台信息(支持的手机类型)保存。
2.2.b 用setProcStatCodeID将item的状态置为ITEM_STATUS_FAILED

OPFLAG_DELETE的处理:
1. 调用modifyHandling方法,用方法getItemVOList去获得List,然后对每个 ISMPItemVO调用checkItemAlreadyExist,“This is to check that item exists or a new item”,也是让人迷惑,搞不懂。

processContent返回到processBatchNotify,processBatchNotify调用setProcStatCodeID设置BatchStatusVO的状态。

2009年8月12日星期三

CMSEntity之八,内容描述文件的获取和初步处理

BSG获取内容描述文件并做初步处理,是在ContentFetcherJob中完成的,真正干活的是类ContentAdaptor,它用 ContentParser去解析内容描述文件,解析后的结果是ContentsDescVO,这个类不知道有什么意义,其实里面就只有 List,ContentVO和内容描述文件里面的tag可以说是一一对应的。然后ContentAdaptor调用 ItemSubmissionConverter.fromXMLVO将前面得到的ContentsDescVO转换为 List。比较令人关注的是在内容描述文件中,“languagesSupported”这个标签的处理,支 持的语言,这个信息的处理流程如下:

ItemBREWPropertyTypeVO.getLanguagesSupported()
ISMPItemLanguagesVO.setIsmpLanguageId()
ISMPItemVO.setItemLanguageVOList()
ContentItemVO.setItemVOList()

语言信息最终保存到了ISMPItemVO中,而ISMPItemVO又被包含在ContentItemVO。至于为什么要做ContentsDescVO到List的转换,目前还不知道动机和目的。

回 到ContentAdaptor,fromXMLVO完后,ContentAdaptor继续调用saveContentItemInfo将 ContentItemVO信息保存到数据库中,这里主要操作的是数据库中的表:ISMP_CONTENT table 和 ISMP_ITEM table。然后ContentAdaptor调用fetchZipFile去获得内容实体问题,在6号的博客里提到了内容实体文件是zip文件,内容实 体文件的地址保存在内容描述文件中。

最后还有个很重要的步骤,ContentAdaptor调用updateBatchStatus去更新前面处理的item的状态,将其置为saved。后面的操作都是基于item和content状态的。

BatchStatusVO 里面的内容不知道有什么作用,但是后面很大地方要用到它,一个比较关键的问题是前面提到的ContentItemVO与BatchStatusVO是如何 建立联系的,在ContentItemVO中有一个成员batchId,这个id在ContentItemVO和BatchStatusVO中都有保存, 所以可以通过这个id找到对方。这个过程可以参看ContentHandler的方法getContentByBatchID。

2009年8月6日星期四

CMSEntity之七,内容的提交

内容上传的流程今天才弄明白点,以前一种存在误解,直到前天和昨天看代码有疑问(为什么在product Info和product content map文件里面都没有具体的content和item信息,因为按以前的理解,在product info文件理应该要包含某个product下有多少content,每个content有包含哪些item这类信息,但是在product Info和product content map文件product Info和product content map文件里没有发现,而这些信息是在ICMS _SYNCYYYYMMDDNNNN.REQ文件里),然后昨天今天看电信文档才发现一直理解错误。

CMS的流程是:

1,ISMP或者NMSC发起contentSyncNotify的webservice给BSG
2,BSG处 理这个soap,获得content description file(内容描述文件),这个文件的文件名为:ICMS _SYNCYYYYMMDDNNNN.REQ,其中YMDN代表年月日时间。这个文件被存放在本地路径:/home/qualcomm/CMS/REQ, 这个信息可用从文件BSGCMSconfig.xml配置文件里查询到。
3,BSG开始处理内容描述文件,描述文件里有些信息很重要,值得注意的有:
operation项,它是一个整型的值,1表示增加,2表示修改,3表示删除。BREW要求这里必须是1,因为谢天曾经提到过,BREW只有添加操作没有别的,但是如果实现删除操作?对于某项内容,把要删除的项剔除,剩下的内容作为新的重新提交,这样实现的删除。
manufacturerPartNumber项,这一项在描述文件里就是用实体文件名的数字作为填充值,但是这个值是不对的,在后面的操作中manufacturerPartNumber要改成ContentID。
4,BSG将描述文件里的内容填写到相应的数据库表中,处理完后,下载内容实体文件,实体文件的下载地址是在描述文件里指定好的,在描述文件的contentURL项中,指定了到何处去取实体文件,实体文件是一个ZIP文件。
5,BSG会生成一个Meta文件(xml)和实体文件一起提交给BMC,Meta文件和实体文件存放在本地目录:/home/qualcomm/item_submit_zip (在BSGCMSconfig.xml配置文件的zipDirBase项)
6, 提交完内容后,BSG产生一个content synchronization feedback(内容同步反馈文件),保存到ISMP的指定FTP目录中。这个反馈文件在本地目录也有保存:/home/qualcomm/CMS /RSP/Archive (在BSGCMSconfig.xml配置文件的localFeedBackArchiveFolder项),到此内容提交结束。
7,BSG去轮询以获得product info,product content map和charging info文件提交产品信息及计价策略文件

2009年7月31日星期五

CMSEntity之六,内容提交和产品定价的规则

两个重要概念:
1. 定价方法(price method):
  • 免费试用(Demo):用户免费使用
  • 购买(Purchase):一次性购买
  • 订购(Subscription):包月

2. 定价基础(price basis):
  • 使用次数(number of uses):应用可使用的最大次数
  • 使用期限(expiration data):到期截止日
  • 使用天数(number of days):可使用的天数
  • 使用时长(elapsed time):应用在终端上可以使用的时长,以分钟为单位

产品以这两个概念为定价的基础,剩下两个辅助概念,一个是price,一个是price value,这些就是四元组。price表示实际的费用,例如某个产品是5元10次,那么price就是5元,而price value就是10。

规则:
  • 三种定价方法相互独立,也就是说给某个内容定价时,可以使用其中的一个,两个,或者三个。
  • 选择免费试用(Demo)方法时
    • 只有三种定价基础可以用,即使用次数,使用天数,使用时长。使用期限不可用
    • 在三种可用的定价基础中,只能选择其中一种,其缺省值为不超过10次,不超过10分钟或不超过一天。
  • 在选择订购(subscription)定价方法时
    • 定价基础是可以忽略的
  • 在选择购买(purchase)定价方法时
    • 四种定价基础(使用次数,使用期限,使用天数,使用时长)全部可用
    • 虽然四种定价基础都可用,但是只能选择其中的一种使用,例如,如果选用使用次数,那就不能再选择使用天数
    • 对 于某个被选择的定价基础,最多可用三个price value(这一块可用参考代码,常量PURCHASE_PRICE_OPTION_SIZE应该就是这么来的),例如:5元包10天,10元包60天, 和20元无限使用的三个产品是允许的。但是,5元包10天,10元包60天,15元包100天和20元无限使用的四个产品是不允许的。
    • 无限使用的购买适用于任意一种定价基础。例如:使用次数(无限次),使用天数(无限天),使用时长(无限时长),使用期限(无限期限)。

合法产品示例:

内容1有4个产品:

* 产品11:免费试用(Demo)1天
* 产品12:2元包月
* 产品13:2元包10天
* 产品14:20元无限使用

内容2有5个产品:

* 产品21:免费试用(Demo)10分钟
* 产品22:3元包月
* 产品23:2元包5次
* 产品24:5元包15次
* 产品25:20元无限使用


非法产品的示例:

内容3有4个产品:

* 产品31:免费试用(Demo)15次
* 产品32:2元包月
* 产品33:2元包10天
* 产品34:20元无限使用

内容4有4个产品:

* 产品41:免费试用(Demo)10分钟
* 产品42:3元包月
* 产品43:2元包5次
* 产品44:5元包10天

内容5有5个产品:

* 产品51:3元包月
* 产品52:2元包1小时
* 产品53:5元包10小时
* 产品54:10元包40小时
* 产品55:20元无限使用

2009年7月30日星期四

CMSEntity之五,price plan的生成

接昨日:
prepareMfgPricePlan用于构建manufacture price plan文件,它会调用createMfgPriceMethod方 法,前面提到了,方法createPriceMethod会创建price method,这些信息会被manufacturer price plan文件和operation price plan文件使用,当createPriceMethod创建好price method后,这些信息保存在ProductPriceMethodVO,查看ProductPriceMethodVO的成员可以,其实里面放的就是四元组:price method,price value,price basis和price。同时createPriceMethod也会调用setPriceMethod将这些信息保存到ProductVO(ProductVO有一个ProductPriceMethodVO型的成员priceMethod)。

再 回到prepareMfgPricePlan,它调用createMfgPriceMethod方法,createMfgPriceMethod其实就是 从ProductVO中得到ProductPriceMethodVO信息,然后将这些信息分别填到MfgPriceMethodTypeVO中去。 prepareMfgPricePlan然后就是一个逻辑:因为调用createMfgPriceMethod后得到一个 MfgPriceMethodTypeVO对象mpmo,然后用这个对象的Method_type作为一个key,去mpmoMap里去查找,看是能查 到,如果能查到,将查到的值赋给prevMpmo。

为了清楚地描述这个逻辑,现在做下面的定义:
当前的MfgPriceMethodTypeVO对象:mpmo
原有的MfgPriceMethodTypeVO对象:prevMpmo(如果原有的对象存在,这是用Method_type作为一个key,去mpmoMap里查找的)
四元组:price method,price,price value,price basis
price mothod可能的值有:Demo,Purchase,Subscription
price valude可能的值有:数值(1,2,3等),UNLIMITED
price basis可能的值有:USES,TIME,DAYS
OPTION:是Manufacturer price plan文件的一个tag,它包含四元组的price和price value两项。

逻辑:
1,如果当前的product value是UNLIMITED,那么将当前的productVO和method type存放到一个map里:productsBasisUnlimited.put(key, pv);

2,判断是否有prevMpmo,如果有,进入步骤3,如果没有,直接到步骤5。

3.a, 当prevMpmo存在时,如果当前的method type是Purchase,将optionSize赋值为3:optionSize = Constants.PURCHASE_PRICE_OPTION_SIZE;(这里需要解释的是为什么常量 PURCHASE_PRICE_OPTION_SIZE为3而不是别的值,今天看到“关于BREW内容提交和产品定价的规则说明”邮件才知道,这是 BREW规定的。“对于任意一个被选中的定价基础,BREW允许最多三个定价基础值”,所以常量PURCHASE_PRICE_OPTION_SIZE的 值为3).

3.b,当prevMpmo存在时,如果当前的method type不是Purchase,将optionSize赋值为1:optionSize = Constants.DEMO_PRICE_OPTION_SIZE(解释见上)。

4,判读mpmo和prevMpmo的price basis是否相等,相等进入4.1,不等进入4.2

4.1.a,获取prevMpmo的OPTION的size,如果大于optionSize,报错

4.1.b, 如果小于optionSize,调用方法checkDuplicatePriceValue判断prevMpmo和mpmo是否相等(也就是判断两者包含 的四元组是否完全相等),如果相等就抛出异常。如果不等,将当前mpmo的OPTINO加入到原有prevMpmo的OPTION 中:prevMpmo.getOPTION().addAll(mpmo.getOPTION());

4.2.a,price basis不相等,调用方法updateBasisUnlimited,这个方法判断prevMpmo和mpmo的price basis是否是UNLIMITED,如果是就将其price basis赋给另一方,如果都不是,返回false。

4.2.b,如果updateBasisUnlimited返回true,将当前mpmo的OPTINO加入到原有prevMpmo的OPTION中:prevMpmo.getOPTION().addAll(mpmo.getOPTION());否则报错。

5,将当前mpmo加入map:mpmoMap.put(key, mpmo);

逻辑完后,prepareMfgPricePlan会调用ProductParserImpl类的方法prepareMfgPricePlanMeta去生成meta文件,到此为止。

prepareMfgPricePlan 执行完毕后,返回到processCntProductAssocList,它会调用submitMfgPricePlan方法,这个方法最终会调用 BMCClientImpl类的createMfrPricePlan,这个方法的功能是:This method will invoke the BMC service and submits the manufacturer price plan。到此为止,manufacturer price plan文件的生成及提交就完成了。

processCntProductAssocList继续调用prepareOprPricePlan来生成operation price plan文件。prepareOprPricePlan调用createOprPriceMethod来 说生成price method,这个方法和createMfgPriceMethod(如前述)的作用是一样的。prepareOprPricePlan后面的工作就很简 单了。这里有个问题,相对于prepareMfgPricePlan,prepareOprPricePlan的逻辑非常简单,根本原因就是 MfgPriceMethodTypeVO和OprPricePlanMethodTypeVO的成员虽然很相似,都是四元组,但是两者有一个很大的区别 是:OprPricePlanMethodTypeVO的PricePointTypeVO变量是一个值,而MfgPriceMethodTypeVO中 是一个PricePointTypeVO的List。这导致了prepareMfgPricePlan比prepareOprPricePlan要简单得 多。

2009年7月29日星期三

CMSEntity之四,三类文件的处理

ProductHandler类是真正从product,product content和charging info转换为price plan,入口可以认为是processPricePlans方法。

首 先它调用getUnprocessedCntProductAssoc,这个方法的功能是:1.Get Unprocessed Content and Product associate VO from DB 2.Sort the unprocessed list。这个方法涉及到了ContentProductAssocVO,这里有个机制去判断一个ContentProductAssocVO是否已经被 处理过,逻辑上应该是先比较几个ID:IsmpContentId,IsmpProductId和OpFlag,如果相同,再比较时间戳。但是具体的逻辑 没细看。

获得Content and Product associate VO list后,processPricePlans继续调用processCntProductAssocList, 这个方法的功能是:Fetch all the content and product pair in the content_product_mapping table for this particular content_id。注意这里面提到了CONTENT_PROD_MAP table,这个表很重要。processCntProductAssocList方法调用fetchContent来获得ContentItemVO,注意fetchContent的参数是contentID。

然后processCntProductAssocList调用fetchProcducts, 这个方法有点复杂,它用参数返回好几个map(cntProducts,deletedCntProdMap和omittedCntProdMap,把哪 些product放到哪一个map里是更加opflag来判断的,另外,处理出错的情况都放到omittedCntProdMap里去。)。这个方法会调 用createPriceMethod,这个方法非常重要,它用来生成price method,在manufacture price plan和operation price plan里都能看见(method标签),它的参数有ChargingPolicyVO和ProductVO,前者保存的是电信的收费信息。该方法首先得到charg info的收费方式,在电信文档的附录D中有描述,包括BREW系统原有计费策略和与之相应的ISMP产品计费策略。这些策略一共有九种,但是在createPriceMethod里只处理了5种情况,不知道为什么。

在 fetchProducts的最后有一个逻辑,当OpFlag为delete时,把product放入deletedCntProdMap,如果 OpFlag不是delete,那就是add,但是在之前还要进行判断,如果要add的项也被包含在deletedCntProdMap(两者的 productID相同,也就是一个product既已经被包括在deletedCntProdMap,现在又要将其add),那么就要判断add的项和 delete的项两者的时间戳,如果要add项的时间戳要早于要delete项的时间戳,那就什么也不干(表示这个product最终是要被删除的),反 之,就将给product加入到cntProducts中。

processCntProductAssocList会调用fetchProducts两次,然后processCntProductAssocList调用prepareMfgPricePlan方 法,它的功能是:Prepare the MFG Price plan and generate the meta file. The ISMP price plan to BMC price plan mapping logic are locate inside this method. 说明这个方法很重要,manufacturer price plan文件就是由它构建的。

2009年7月28日星期二

CMSEntity之三,谈话整理

昨天下午谈话的整理:
电信这边的产品信息包括:product info,product content和charging info。电信的收费信息是针对product而言的,也就是一个产品它有计费信息,而不是一个内容或者一个电信item有它的计费信息。而BREW这 边,计费信息是针对manufacturer partner number的,每个manufacturer partner number下面包含有多个item,一个manufacturer partner number就相当于一个组,包含多个BREW item,计费信息就是针对这个组的。目前是把manufacturer partner number和电信那边的content视为等价,但其实两者是不同概念。

price plan是BREW这边的概念,它是由product info,product content和charging info这三个文件导出的,四元组:price basis,price method,price value和就包含在price plan中,所以price plan是非常重要的。

Meta.xml和item.zip一起再压缩成一个zip给BMC(这个Meta.xml和item.zip最好能看看里面的内容)。

数 据库的几个表,ISMP_Content,ISMP_Item等等(ISMP Content number什么的,作为key,这个也不知道是什么东西),这个没搞清楚,还有对应product info,product content和charging info每个文件都有一个表,这些表要弄清楚。

对BREW手机和ADS来说,catalog是有多个 的,每个catalog是针对语言来说的,例如英语有英语的一个catalog,汉语有汉语的一个catalog,其它语言有其自己的catalog。每 个catalog可以想象其为一颗树,和pc机上的目录结构是一样的,catalog下有category,也有subcatalog,catalog下 也可以有item,subcatalog下有item等等。

manufacture price plan和operation price plan是高通的遗留东西,但是现在还在使用,两者的区别是operation price plan有change list,但是manufacture price plan没有。

在CMSEntity里的dao包中,有关的表有:
PRODUCT_INFO table,PRODUCT_CHARGE_INFO table,PRODUCT_CHARGE_MODE table,CONTENT_PROD_MAP table,ISMP_ITEM table,ISMP_ITEM_MODELS table,ISMP_ITEM_LANGUAGES table,CHANGE_LIST table,Ismp_Region table等等,目前重点关注PRODUCT_INFO table,PRODUCT_CHARGE_INFO table,PRODUCT_CHARGE_MODE table,CONTENT_PROD_MAP table。

2009年7月27日星期一

CMSEntity之二

com.qualcomm.bss.bsg.cms.job.ContentProcessorJob.execute会调用 com.qualcomm.bss.bsg.cms.process.ContentHandler.invokeProcess,我觉得 ContentHandler应该是真正干活的,它来解析map文件并把内容保存到数据库中。但是 ContentHandler.invokeProcess很简单,它调用了 ContentHandler.commonStorageHandling,在这个方法中对ISMPItemVO,ContentItemVO进行了一 番操作,但是也没看出头绪。

TerminalInfoLoadJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.TerminalInfoLoadJob.execute->com.qualcomm.bss.bsg.cms.job.TerminalInfoLoadJob.loadData

ContentFetcherJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.ContentFetcherJob.execute->com.qualcomm.bss.bsg.cms.process.ContentAdaptor.invokeProcess
->com.qualcomm.bss.bsg.cms.CDParserImpl.parseCDFile

ContentProcessorJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.ContentProcessorJob.execute->com.qualcomm.bss.bsg.cms.process.ContentHandler.invokeProcess->
com.qualcomm.bss.bsg.cms.process.ContentHandler.commonStorageHandling

ProductInfoFetchJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.ProductInfoFetchJob.execute->com.qualcomm.bss.bsg.cms.process.prodAdaptor.processProductFiles

ProductAdaptor里的方法大有可为,很多事情都是在ProductAdaptor里 面做的,看ProductAdaptor类前面的解释,它的功能包括:1,获取product info文件并保存到本地文件夹;2,解析product info文件并保存到数据库;3,获取product content文件并保存到本地文件夹;4,解析product content文件并将数据保存至数据库;5,获取charge info文件并保存到本地文件夹;6,解析charge info文件并将数据保存至数据库;
由此可见,三个map文件都是在ProductAdaptor类里处理的,所以有必要对ProductAdaptor类中的每个方法都细读。

ProductAdaptor.processProductFiles, 这个方法的功能是用ftp获取三个map文件,即product info,product content和charging info。这个方法里用到了类ProductFtpHandler,并对ProductParser类进行了实例化。ProductFtpHandler 是用来获取相应的map文件的,调用它的fetchProductInfoFilesfetchProductContentFilesfetchChargingInfoFiles就能获得相应的文件。每当获得相应的文件后,processProductFiles会调用方法来解析获得的文件,这些方法包括processProductInfoFileprocessProductContentFilesprocessChargingFiles,从名字就很好判断它们的作用。

前面提到processProductFiles实例化了ProductParser,这个类顾名思义,是用来解析product info文件的,但是看这个类的解释,又不仅仅是。ProductParser类只是一个接口,它的实现类是ProductParserImpl, 这个类最前面的注释说:“This class is used to parse product info ,Product content mapping , charging policy info files. Also creates price plan xml files.”,从这段注释可知这个ProductParserImpl不仅仅是解析product info文件,它把三个map文件都解析了,同时生成了price plan文件。前面提到的processProductInfoFileprocessProductContentFilesprocessChargingFiles方法都使用了ProductParser的实例来解析相应的文件。

ProcessProductInfoFile 先是调用了ProductParserImpl.parseProductFile解析product info文件,它的返回值是ProductInfoVO,然后ProcessProductInfoFile再调用 ProductDAO.addProductInfo将ProductInfoVO的信息写入到数据库中。
processProductContentFiles 处理product content文件,它的处理过程和ProcessProductInfoFile差不多,先是调用 ProductParserImpl.parseProductContentFile解析product content文件,返回值是ProductContentVO,在将ProductContentVO的信息保存到数据库之 前,processProductContentFiles还调用了applyProductContent方法,这个方法是“used to apply products to contents”,不知道是什么意思。但是看代码,这里是生成product和content的关系,这些关系都存放在contentProductAssocVO,这是一个很重要的对象,后面的操作要经常使用这个对象。然后ProcessProductInfoFile再调用ProductDAO.applyProductContent将ProductContentVO的信息写入到数据库中。
processChargingFiles的流程和前两个相似,不赘述。

ProductProcessJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.ProductProcessJob.execute->com.qualcomm.bss.bsg.cms.process.ProductHandler.processPricePlans

ProductHandler类是生从电信的那一套(也就是product,content,item和charging info)转换成BREW识别的一套(item和price handle)的关键。具体的实现与数据库非常相关,目前暂时还没研究。

ReleaseProcessJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.ReleaseProcessJob.execute->com.qualcomm.bss.bsg.cms.process.ReleaseHandler.invokeProcess

MasterSwitchJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.scheduleMasterSwitch->

FeedBackFileUpLoaderJob:
com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.scheduleFeedBackUpLoadProcess->com.qualcomm.bss.bsg.cms.job.FeedBackFileUpLoaderJob

2009年7月24日星期五

CMSEntity之一,各种Job和VO

contentFetcherJob
ContentProcessorJob

ProductInfoFetchJob
ProductProcessJob

com.chinatelecom.ismp.contentpublish.req包的类ContentSyncNotifyReq

com.chinatelecom.ismp.contentpublish.ContentPublishedReqAdapter
com.chinatelecom.ismp.contentpublish包里的方法ContentPublishedReqAdapterSoapBindingImpl::contentSyncNotify

前 面的关系有点乱,到现在还没理清,按流程应该是ISMP先给BSG发一个contentSyncNotify(一个SOAP call),BSG收到这个notify后去解析它的内容,获得content description文件的存放地址和目录,但是现在还不知道是在哪里处理这个notify的,本以为是在 com.qualcomm.bss.bsg.cms.servicelaye.BSGWebServiceServlet处理这个notify的,但是看 了这个类的doGet方法,感觉这个方法没有处理notify。CMSWebServices和CMSEntity两个工程里面都没找到,这个暂且放下, 以后再找找。

目前看com.chinatelecom.ismp.contentpublish。ContentPublishedReqAdapterSoapBindingImpl::contentSyncNotify是处理notify的(但不知道它是如何被调用的,好像用了一个axis2的东西,google了一下,这是Apache的一个包,但是它是干什么用的没细看)。
方法contentSyncNotify使用了contentSyncNotifyReq, 这个类其实无甚特别,但是其中有一段代码搞得我很迷惑:static{... ...},这段代码和org.apache.axis.description有关,不知道是干嘛的,留到后面再研究。然后 contentSyncNotify调用saveContentSyncInfo将得到的信息保存到数据库中,这个应该是content description文件的存放地址和目录。

com.qualcomm.bss.bsg.cms.parser是一个接口,类CDParserImpl实现了这个接口,里面有个方法:parseCDFile,这个方法就是用来解析content description文件的。
com.qualcomm.bss.bsg.cms.process.ContentAdaptor类中的方法:invokeProcess,调用了parseCDFile。
然 后com.qualcomm.bss.bsg.cms.job.ContentFetcherJob的方法execute中会调用 contentAdaptor.invokeProcess。这个ContentFetcherJob是从StatefulJob继承的类,也就是它是一 个Quterz的类,周期性地执行,
在com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet的init方法中会创建ContentFetcherJob,并对它进行调度。

所 以这几步的流程 是:com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init->com.qualcomm.bss.bsg.cms.job.ContentFetcherJob.execute->com.qualcomm.bss.bsg.cms.process.ContentAdaptor.invokeProcess
->com.qualcomm.bss.bsg.cms.CDParserImpl.parseCDFile。

在 ContentAdaptor类里有个方法fetchFile,这个方法其实就是从给定的地址中用FTP去取文件,invokeProcess调用 fetchFile去获取content description文件,content description文件的地址,在前面提到,content description文件的存放地址和目录已经保存在数据库中了。

content description文件解析完后,ContentAdaptor::invokeProcess会调用fetchZipFile去获取zip文件,这 些zip文件就是product_info.map,content_product.map和charge_info.map文件了。文件被取回 后,ContentAdaptor::invokeProcess还调用了:
batchStatusVO.setProcStatCodeID(Constants.BATCH_STATUS_SAVED);
updateBatchStatus(batchStatusVO);
这 两个调用貌似很重要,因为zip文件取回后就要进入下一步的处理,即将这三个map文件解析并将相应的内容填到数据库中,但是处理三个map文件也是一个 job,即ContentProcessJob,这个job也是在 com.qualcomm.bss.bsg.cms.servicelayer.BSGCMSServlet.init被创建初始化并调度的。也就是说, 它一直周期性地试图去处理三个map文件,但是并不是时时刻刻都有map文件需要处理(因为不是每时每刻都有产品要提交到BMC上去),所以当有新的 map文件被取回后,必须要有一种方法通知ContentProcessJob,现在有新的map文件需要处理了。这个方法应该就是 ContentAdaptor::invokeProcess在取回map文件后,向数据库里写了某些信息,就相当于置了某些标志位,然后 ContentProcessJob在处理过程中首先是查询数据库,获得信息得知是否有新的map文件需要处理。

com.qualcomm.bss.bsg.cms.job.ContentProcessorJob
com.qualcomm.bss.bsg.cms.process.invokeProcess
com.qualcomm.bss.bsg.cms.process.getSuitableBatch

ISMPItemVO
ContentItemVO

2009年7月22日星期三

ISMPEntity之三,2.x和3.x对包月非包月业务的处理流程

包com.qualcomm.bss.bsg.ismpentity.ismpcore中的类ProductPriceObject值得关注,它里面包含了几乎所有重要信息:priceHandle,productID, priceMethod,priceBasis,optionPrice(就是pricevalue),regionId,contentId和optionValue。所以得到ProductPriceObject的实例就可以知道productID等信息。
在 包com.qualcomm.bss.bsg.ismpentity.devices的类Device中有个方 法:getProductPriceObject,可以用它获得ProductPriceObject,getProductPriceObject其实 是去查询数据库来获得ProductPriceObject:
productPriceObject = operationalDataDAO.getPriceHandleMap(String.valueOf(priceHandle), itemId, regionId);

如 果在数据库里查找不到,那么getProductPriceObject会去ISMPConfig里查找postactivition的地 址:String strPostACtivationIPANdURL = ISMPConfig.postActivationURL,根据这个地址,调用方法submitRequest,实际上是去这个地址去访问,返回值是个 字符串,里面包含了ProductPriceObject的所有信息。

3.x手机,下载非包月应用,处理的流程:
1,用户选择好应用后,点击下载
2,手机发出下载鉴权请求给ADS
3,ADS将请求发给BSG
4,经过BSG的router,最终ISMPEntity收到鉴权请求。
5,RequestHandler::doGet->ISMPEntity::invokeService->Locator::locateDeviceHandle->Device::handleRequest
6,Device::handleRequest根据请求的类型来做相应处理,有三种类型的请求:
public enum RequestTypeStatusCodes {
PurAuth,
MsgDownloadAck,
DeleteAck
}
因为是非包月下载请求,所以这里是PurAuth。
7,用Device::getProductPriceObject获得ProductPriceObject对象,该包含了pricehandle,productID等等信息。
8,进入PurAuth处理流程,判读是否包月(这里是非包月),进入非包月处理分支,获得PurchaseAuthorizationHandlerImpl对象。
9, 进入PurchaseAuthorizationHandlerImpl::handleRequest方法,生成 messageID(messageID是联系request和response的桥梁,ISMPEntity会有多个不同的request放在 queue里并发送给ISMP,同时ISMP也会有多个不同的response,request和response必须成对出现,有request必须有 response,某对request和response之间就靠messageID联系,它们必有相同的messageID)并和其它信息 (ismi,itemID,pricehandle等)一起保存到数据库。
10,调用preparePDU构建PDU,然后调用PDURequestHandlerInterface::sendPDU发送PDU。

3.x手机,下载包月应用,处理的流程:
前7步和3.x非包月相同。
8, 进入PurAuth处理流程,判读是包月,进入包月处理分支,获得CreateSubscriptionHandlerImpl对象。
9,进入CreateSubscriptionHandlerImpl::handleRequest方法,包月的创建是用SOAP的,所以调用了方法prepareSOAPRequest去构建并发送SOAP request。

当 ISMP返回成功后,3.x下载包月应用的流程应该和下载非包月的流程一样,再次进入 PurchaseAuthorizationHandlerImpl::handleRequest方法去发送PDU,但是3.x如果再次进入 PurchaseAuthorizationHandlerImpl的处理流程,目前还没有看出来(参考电信文档第8.3.2节"有下载鉴权的包月应用下 载",BSG和ISMP之间是先用SOAP发送了createSubscriptionReq和createSubscriptionResp,然后再和 下载非包月应用一样,发送AuthPrice request和response。我估计因为SOAP还是建立在http协议之上的,所以当ISMP发送createSubscription response给BSG后,ISMPEntitiy还是先进入RequestHandler::doGet,后面的流程就和下载非包月一样了)。

2.x手机,下载非包月应用,处理流程:
前10步和3.x非包月相同。
11,AuthPrice的request PDU发送后,PurchaseAuthorizationHandlerImpl::handleRequest返回到TwoXDeviceImpl::handleRequest。
12, 调用Device::duplicateMsgDownloadAckCheck,这个方法是用来验证MsgDownloadAck是否是已经发送过的 (duplicated),如果是,表明MsgDownloadAck已经被处理过,不用再处理。如果不是,到13步。
13,进入DownloadConfirmationHandlerImpl,调用DownloadConfirmationHandlerImpl::handleRequest方法,发送AuthPricecnfm的PDU。

2009年7月21日星期二

ISMPEntity之二,PDU收发

PDU(Protocol Data Unit 协议数据单元),可以理解为传输协议数据的包,因为电信定义了ISMAP协议,必然有相应格式的包来传输协议的数据,这很好理解,每种协议有其各自的帧格 式。这个可以查看中国电信的文档,附录里有比较详细的介绍。PDU主要分消息头和消息体,消息头包括:PDU的长度,命令ID和序号。命令ID有好多种, 其中有:id_BindReq,id_EnquireLinkReq,id_AuthPriceReq等等,从这些命令ID的名字就一目了然,知道它们是 的作用是什么。

在com.qualcomm.bss.bsg.ismpentity.pdu包中,PDU的基类是BasePDU,是个抽 象类,它的作用也就是给PDU搭个框架,把PDU的公共部分都设置了,剩下和各个命令相关的部分都放到特定命令里去设置。PDU的派生类众多,不过应该都 是大同小异,例如派生类:BindResponsePDU,EnquireLinkRequestPDU等等,从名字就知道它们是构造何种PDU的。

包 com.qualcomm.bss.bsg.ismpentity.ismaphandler有点搞头,里面有些类值得关注,首先是接口 PDURequestHandlerInterface,它包含了一些方法,它们用于发送异步PDU给ISMP,这说明ISMPEntity与ISMP之 间是用异步PDU进行通信的?这个还有待证实。类PDURequestHandlerImpl实现了接口 PDURequestHandlerInterface,具体的实现没细看。

类PDUConnection,对socket进行了封装, 从它的方法:connect和disconnect就很明显,不过这个类的职责并不明晰,它不但包括了对socket的封装,而且它还会发送bind和 unbind的PDU,这一点我决定没设计好,这两个应该是不相关的。

类PDUTransmitter是用来发送PDU的,它用到了 PDUConnection,这是理所当然,它还用到了一个类:PDUWrapper,这个类我没看太明白,不知道有什么用,不过它比较简单,可以暂且放 在一边。PDUTransmitter有个方法generateSeqId产生所谓的sequence ID,我估计这个sequence ID就是电信文档里提到的“用于请求和响应间保持联系的序号”,不过这还是有待证实。在发送的时候用方法 associateSeqToPDUWrapper将sequence ID和PDUWrapper进行绑定,它们是一个键值对,每个sequence ID都对应唯一的PDUWrapper。这个键值对是有用的,在类PDUReceiver中要用到的。

类PDUReceiver和PDUTransmitter做相反的事。

PDU 这一块现在还是有点迷糊,目前看,PDU的操作其实也可以分为两层,上层包括PDURequestsHandlerInterface和 PDURequestsHandlerImpl,这两个类一个是接口一个是实现,它们是给上层用的,其中有个方法是sendPDU,这个方法用来发送 PDU。而其实待发的PDU加入到一个队列中PDUQueue,最终将某个PDU发送出去的是类PDUTransmitter实现的。

调用的大致流程:
RequestHandler::doGet->ISMPEntity::invokeService->Locator::locateDeviceHandle->Device::handleRequest->HandlerInterface::handleRequest->PDURequestHandlerInterface::sendPDU->pduQueue::addToQueue-
>PDUTransmitter->sendPDUBytesToISMP

2009年7月20日星期一

ISMPEntity之一

ISMPEntity的主要作用:
与ISMP进行绑定或者解绑定(bind,unbind),并维持心跳(heartbeat);管理BDS(BREW Delivery System)与ISMP之间的通信信道,BDS的实时流(用户下载,订购等信息)都是通过ISMPEntity转达给ISMP的。

request 实时流的入口:RequestHandler.java: public void doGet(HttpServletRequest request, HttpServletResponse response),doGet中会调用ISMPEntity.java的方法invokeService,这个方法比较重要,所有手机过来的实时 request,都要经过这个方法,该方法根据它的参数来确认发送request的手持终端的类型,通过这个类型,从类Locator获得相应设备的句 柄,调用相应设备的方法handleRequest来处理终端的request。
其实这里使用的就是多态,设备有多种类型,具体设备都是从一个基类派生的,invokeService根据它的参数确定相应的设备,然后调用设备的方法来处理request,实质上就是使用多态来实现。

前面提到的设备的基类就是DeviceInterface,是位于com.qualcomm.bss.bsg.ismpentity.devices包里的,它是一个接口,里面只有一个方法handleRequest, 抽象类Device实现了这个接口。这个类里面的一些方法有必要关注,但目前还没细看。类TwoXDeviceImpl和 ThreeXDeviceImpl都是从Device派生的,这两个类的名字就很明显了,老谭曾经提到,目前高通有两个平台:较老的2系列和较新的3系 列,这两个类就分别对应的2系列设备和3系列设备。

类HandlerInterface也颇为重要,位于 com.qualcomm.bss.bsg.ismpentity.handler包,它也是个接口,和DeviceInterface本质是相似的,它 对应的是服务的概念,类AppDeletionHandlerImpl,CreateSubscriptionHandlerImpl和 DownloadConfirmationHandlerImpl从HandlerInterface派生,根据三个派生类的名字也可以大概明白它们的作 用:一个处理删除,一个处理包月的创建,一个处理下载确认。注意HandlerInterface接口中也有个很重要的方法叫handleRequest,这个和前面提到的设备的handleRequest不要混淆。在HandlerInterface接口的handleRequest中对收到的request进行了解析,request参考电信文档第八章和第九章,里面有详细request格式介绍。handleRequest按照这个格式进行解析,因为要取出相应的信息来构建PDU

2009年7月17日星期五

CommonProcess之二

commonce process模块是用Quartz进行调度的,网上说它是个“作业调度集”,因为common process模块是用轮询的方式去获得三类文件的,所以考虑把对三类文件的轮询看作是各个作业,用Quartz来进行调度。 BSGCommonJob.java里的类BSGCommonJob实现了接口StatefulJob,这个接口也是Quartz里的。

有 了GSGCommonJob类,还要有一个调度类来调度作业,根据Quartz框架,这就涉及到触发器,有两个最常用的是SimpleTrigger和 CronTrigger,在代码里用的是SimpleTrigger,调度类是BSGCommonScheduler.java的 BSGCommonScheduler,其中的方法scheduleJob使用了SimpleTrigger。

在 BSGCommonScheduler里使用了JobConfig,这个类包含了所有关于轮询获取三类文件业务的属性和参数,这些属性和参数是从一个配置 文件CommonConfig.xml读取的,这个文件中设置了文件存放的ftp地址,轮询的间隔等等关键的参数。BSGCommonScheduler 要根据这些信息来调度。

servicelayer包里面的两个文件目前看还不太清楚作用,ConfigServlet.java应该是从 CommonConfig.xml里读出配置项并设置。前面提到的调度BSGCommonScheduler是在StartupServlet.java 里的init方法里设置的。

BSGCommonProcess及其三个派生类是真正干活的,各个××××process类几乎包含了所有 操作所需要的内容,包括:数据库,ftp,文件的解析(xml文件的解析是用SAX方式)等等。从ftp相应的地址处获取文件是在方法 initiateFetch完成的,它从ftp地址获取所有的文件。文件被取回后,文件名和时间戳等信息会被存放到数据库的相应的表中,这些操作是用 “VO”包中的类完成的。所有的已经处理或者未处理的文件的名字应该都存储在数据库的相应的表中,这样就可以清楚地知道当前文件处理的状态。文件的处理是 在方法processFiles中,处理的细节有点像状态机,文件有好几个状 态:BSGCommonConstants.FILE_START,BSGCommonConstants.FILE_PROCESSED,BSGCommonConstants.FILE_CORRUPT 和BSGCommonConstants.FILE_FINISH,不同的状态有不同的方法处理,最终使得文件到达 BSGCommonConstants.FILE_FINISH状态。

handler包里面的代码都是用来处理相应xml文件的,因为common process是使用SAX的方式解析xml的,所以类BSGCommonHandler是从DefaultHandler派生的,

request实时流的入口:public void doGet(HttpServletRequest request, HttpServletResponse response)

2009年7月16日星期四

CommonProcess之一

目录D:\Qualcomm\Design\Dec - 05 - 2008\index.htm存放了类图,目前来看是老谭机器上最新的了。

首先我看的是commonprocess的代码,因为commonprocess只是和ftp和DB打交道,看起来可能轻松些。结合类图看代码,可能是因为文档比较老的缘故,和代码并不太一致。BSGCommonJob.java里的类BSGCommonJob, 目前看可能是commonprocesss的入口,至少是一个入口,它就像一个分发器(dispatch),里面有是switch,因为 commonprocess从NMSC里获得三类文件,所以switch里分别处理了四类宏,相对应的处理方法是 ImsiMsisdnProcess,NumberSegmentationProcess和TerminalInfoProcess。

BSGCommonPress是个基类,有几个派生类从它继承,实现相应的文件处理,例如ImsiMsisdnProcess从它派生,处理IMSIMSISDN文件。

凡是带“DAO”的文件,都是和数据库相关的。目前暂时没有详细地研究。

2009年7月15日星期三

中国电信信息园

今天第一次去电信信息园,熟悉了下deploy的流程。也就是如何将war文件安装到WebSphere上。

前期准备:因为3G实验室里的网络要设置成DHCP的,和wipro的不一样,所以首先是要把网络配置好,在桌面的command文件夹中有个bat文件,set_ip_SHRI.bat,用它就可以将网络配置好。

步骤:
1,登录到WebSphere的控制台:地址是http://192.168.26.*:9060/ibm/console/login.do (其中*表示要登录到哪台服务器,比如*可以是8,10等等,9060表示端口号)。登录的用户是qualcomm,密码是test123

2,登录后进入“application->enterprise applications”选项,将会把已经安装的war文件都列出,可以看到各war文件的状态,例如已启动或者未启动。

3,找到需要安装的war文件,这需要登录到相应的服务器上,例如要安装BSGRouter-1.0.0.10.war,首先要知道这个war文件在何处,这需要登录到相应的服务器上去查找,例如登录到服务器:192.168.26.10上,BSG的文件,在所有的服务器上都放在相同的目录下。
命令:进入cmd,用telnet 192.168.26.* (*的意思和前述相同,表示某服务器ip的最后一个数字)。然后进入/home/qualcomm/bsg,到该目录下后,用ls命令查一下,有个叫 “builds”目录或者别的目录(具体的目录名记不清),存放着war文件,找到war文件后,需要将war文件下载到本地。

4,用ftp将war文件下载到本地,下载需要使用ftp协议。
命令:
a,用在命令行里用ftp 192.168.26.*登录到存放war文件的服务器上。
b,用命令:lcd来指定war文件存放的本地路径,例如 lcd c:\表示将war文件存放到本地的c盘根目录下。
c,用bin命令告知服务器在进行ftp文件传输时用二进制方式进行传输,因为war文件是二进制文件,如果不用bin命令,ftp默认的是用ASC码传输的,这样下载的war文件是有问题的。
d,用get命令下载war文件,例如get BSGRouter-1.0.0.10.war,下载BSGRouter-1.0.0.10.war到lcd命令设置的本地目录下。
e,用quit退出ftp

5,下载完成后到本地目录下去check,理论上下载完成后就没有问题了,但是有时候会出现下载到本地后,用WebSphere安装时会报错,所以当把war 文件下载到本地后,最好做一下检查。最主要的检查项是web.xml文件。war文件可以看着是一个zip文件,所以可以直接把war文件的后缀改成.zip,然后解压,找到web.xml文件,简单地查看一下它的内容,没什么明显的问题即可。

6,检查了war文件后就可以回到WebSphere的控制台了,在其中点击Install按钮,开始安装,具体的步骤可以参考training ppt文档,第二步要选择要按照的war文件,这时只要在本地选中即可,后面的步骤基本上只要next就行了,到最后一步貌似有一个“save”的步骤,如果在最后一步发现“save”,点击即可。

7,安装后即可以到WebSphere里去查看,正常的话应该显示war文件已被按照,但是他的状态是一个×的图标,表示改war还没有被start。此时不能立即执行start,因为还需要有配置文件才可以启动。配置文件需要到相应的服务器上去取,他的下载过程和war差不多,不同的是配置文件不被下载到本地,而是直接放到部署war文件的服务器上,例如前面提到的BSGRouter-1.0.0.10.war被安装在服务器192.168.26.10上,那么配置文件也应该被放在10上的相应目录下。

8,假设BSGRouter-1.0.0.10.war的配置文件在服务器192.168.26.8上,先在命令行里用telnet登录到 192.168.26.10,然后用ftp命令ftp 192.168.26.8登录到8上,用户名是qualcomm,密码是test123。登录后用lcd设置存放配置文件的目录,这个目录应该是 /home/qualcomm/bsg/config,设置好后,用bin命令告知ftp用二进制方式进行文件传输(因为配置文件是xml文件,按说不用 bin也应该没问题,但是为了保险起见,还是用bin最安全)。然后用get命令开始文件传输。

9,文件传输完成后,用quit退出ftp,当10的相应的目录下查看,用ls -tsr命令查看,这个命令可以将文件的日期等详细信息列出来,便于更好地查看。

10,check配置文件无误后,就可以在WebSphere的控制台里点击start按钮来启动相应的war了。

2009年5月14日星期四

模板类的定义和紧耦合的两个类如果解决声明问题

前天晚上看了下数据结构的C++描述版,然后就动手写了个链表的类,用到了类模板,我是这样定义的,类ChainNode和Chain分别表示链表 的节点和链表本身,两个类的声明放在名为chain.h的头文件中,而又加了一个名为chain.cpp的源文件来实现这两个类中的方法。因为是最基本的 链表操作,所以很快就写完了,然后在main函数里面加了几天测试代码,编译链接。这时候问题来了,两个类里面的方法都链接报错,说是找不到实现,这让我 很奇怪,因为我明明在chain.cpp里面都有实现,为什么找不到,一开始我考虑到是否是因为在我实现的时候语法不对,因为我知道使用模板类和普通类不 同,有很多要注意的地方,例如实现方法Search时,不能写成:

bool Chain::Search(int i, T& x) {}

而是要写成:

template

bool Chain::Search(int i, T& x) {}

于 是把C++ primer又翻出来,看了类模板,确保实现的时候语法是正确的,但是看来看去也没有发现什么问题,编译的时候总是报错找不到实现。于是我修改了 chain.cpp,故意在里面加入了明显错误的代码,但是即使这样,编译chain.cpp仍然不报错,于是我怀疑难道是chain.cpp没有被加入 到工程以至于没有被编译?又查了半天,排除了这个可能,然后我就郁闷了,到底是怎么回事呢,没道理啊。后来Google了一把,总算找到了原因,那篇文章 是这样解释的:

因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和 实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接 程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。

在C++编程思想里的第15章,模板与容器类,有这么一段解释:

甚至是在定义非内联函数时,模板的头文件中也会放置所有的声明和定义。这似乎违背了通常的头文件规则:“不要在分配存储空间前放置任何东西”,这条规则是为 了防止在连接时的多重定义错误。但模板定义很特殊。由template<>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处 于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板 声明和定义。
有时,也可能为了满足特殊的需要(例如,强制模板实例仅存在于一简单的 Windows DLL文件中)而要在一个独立的C P P文件中放置模板的定义。大多数编译器有一些机制允许这么做,那么我们就必须检查我们特定的编译器说明文档以便使用它。

看到这些解释,我就明白了,把Chain的定义放在chain.cpp里面是没用的,确实会导致链接的时候找不到定义。

今 天上午看C++编程思想,在第三章看到一段话,让我想起以前遇到的一个问题,有一次,碰到了这种情况,有两个类:ClassA和 ClassB,ClassA中有一个成员变量是ClassB,同样,ClassB中也有个成员变量是ClassB。ClassA和ClassB在同一头文 件中声明,这时问题就来了,如果先声明ClassA,那么就会报错,说ClassB找不到,而如果先声明ClassB,那么也会报错,说ClassA找不 到,情况如下:

class ClassA
{
public:
ClassA();
~ClassA();

private:
ClassB m_classb;
int m_A;
};

class ClassB
{
public:
ClassB();
~ClassB();

private:
ClassA m_classa;
int m_B;
};

这 种情况下,如何解决问题?在第三章的某处,先是说明了这么做出错的原因:因为如果试图传递整个对象,那么编译器就必须知道这个对象的全部完全的定义以确定 它的大小以及如何传递它,如果编译器找不到给对象的完整定义,那么就会失败。上面正是这种情况,因为如论是先声明ClassA或者ClassB或者两者两 者都声明,但是因为都没用完整的定义,所以编译器就报错了。那么知道了问题的原因,如何解决?可以稍微修改一下,把成员变量改成指针:ClassB* m_pClassb;和ClassA* m_pClassa; 然后在前面加上声明class ClassA;class ClassB;这样就可以了。为什么?因为编译器知道如何传递一个地址,地址的大小是固定的,而不用管地址里的保存的对象类型如何,它的大小有多大。

当然从设计的角度来讲,这ClassA和ClassB这种设计肯定是很不好的,因为耦合度太高了。

2009年5月10日星期日

UML的依赖,关联,聚合和组合

今天复习了下UML,因为周一和印度人谈话时可能要问这个。拿过老谭的UML面向对象建模与设计翻了翻,看到讲聚合和组合的章节,这个东东一直没搞清楚,于是上Google搜索了一下。在UML中,类之间的关系可以分为:依赖,关联,聚合和组合四类。这四类关系其实都可以看作是某种意义上的关联,从依赖到组合,它们的关联强度由弱到强。

依赖是类与类之间最弱的关联,现实中的例子是,类A的某个方法的参数中使用了类B,这样就可以说类A依赖类B。而关联比依赖更强,例如类A中有一个属性,或者说一个成员变量是类B,那么就说类A关联类B。(但网上有的帖子把这种情况不看作是关联,在这些帖子中关联和依赖的强度是一样的,后一种情况应该算是聚合或者组合。)

聚合,用空心菱形表示,指的是整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构。从而找出一些组成类,该整体类和组成类之间就形成了聚合关系。例如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。需求描述中“包含”、“组成”、“分为…部分”等词常意味着聚合关系。

组合,用实心菱形表示,
也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。部分对象与整体对象之间具有共生死的关系。

区别聚合和组合的关键就是整体和部分的生命周期,如果整体和部分是同生共死,一荣俱荣,一损俱损的,那么这就是组合,否则就是聚合。这可以通过下面的代码来对比:
//聚合
class A
{
public:
A(B
* pb)
{
m_pb
= pb;
}
...
private:
B
* m_b;
};

//组合
class A
{
public:
A()
{
}
...
private:
B m_b;
}


对于聚合来说,它不负责组成部分的创建与删除,也就是说A只使用B,但B的生死A不管。而组合就不同了,
A要全权负责B的生老病死。所以组合的强度比聚合要大。聚合关系是“has-a”关系,组合关系是“contains-a”关系;
聚合关系表示整体与部分的关系比较弱,而组合比较强;聚合关系中代表部分事物的对象与代表聚合事物的对象的生
存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。组合中一旦删除了组合对象,同时也就删除了
代表部分事物的对象。

2009年5月8日星期五

microkernel初步

在讲微核模式和反射模式前,POSA首先提到了adaptable system,我姑且把它翻译成可适配系统吧,这个系统的特点是变动大,经常需要在已有的功能或者模块上添加新的功能,或是应付操作系统改变,第三方库变 动等问题。为了实现这样的系统,提出了微核模式和反射模式。

微核模式将系统的核心功能与外部功能和用户要求的功能分离开,同时它也作为一个容器或者socket,包容那些外部的扩展,并让那些外部的扩展能实现协作。微核典型的应用场景是OS和大型IDE的设计。

微核模式定义了5种组件:internal servers,external servers,adapters,clients和microkernel。

microkernel提供系统核心的底层的功能,为各个组件提供通信机制,对系统的依赖进行封装(例如驱动,其它的组件需要使用硬件服务,这是硬件依赖。这一部分工作也可以丢到下面介绍的internal server里去做。),管理资源。

internal servers是对microkernel的功能扩展,所以也称为子系统,microkernel通过向其发送服务请求来调用其相应的功 能,internal server主要是封装一些硬件和软件的依赖,例如某类硬件的驱动。书中特别提到internal server只能被microkenel访问,我想这也是internal server名字的由来吧。

external servers实现什么功能,看了半天也没看懂,貌似external server是向外提供编程接口的,client可以用external server提供的接口来访问microkernel提供的功能。不过这一段没看懂,到底是不是这样还不知道。(从后面的内容可知,external server自己也是实现了某些功能的,而不是什么功能都没有,只是作为一个桥梁去调用microkernel的功能。)

client这一段也没看太明白,“A client is an application that is associated with exactly one external server.”, 这句话让我很费解,为什么只能跟一个特定的external server相关联呢?一个client就不能使用其它的external server吗?如果不行,那会不会导致client太多了。在提到client和external server时顺带提了adapter模式,这是可以理解的,因为使用adapter使得client和external server之间的耦合更松,更有利于应付变化。

下图是微核模式里各个组件类的类图,从图中可以看出各个类之间的关系,internal server是只能被microkernel访问的,而client是通过adapter和external server来访问使用microkernel的功能。

2009年5月7日星期四

PAC初步和Eclipse的visual editor插件

PAC模式感到很陌生,以前从没接触过,所以先在网上搜索了一番,资料也比较少,看了下Wikipedia里面的介绍,虽然说是和MVC比较相似,而且POSA也是将其和MVC放在一起介绍的,但是我觉得他是层模式和MVC模式的混合更合适。PAC从整体来看,它也是一个层结构,具体说应该是一个树状的层结构,树中的每一个节点,称为一个agent。这个层次树一般分为上层agent,中层agent和低层agent,如图一所示:

从图一可以清晰地看出树状的层结构,顶层agent提供系统的核心功能,中层和低层的agent都依赖它。低层agent和用户打交道,接收用户的输入和操作。中层agent联接上层和低层agent(这三层的功能和关系,我没看太懂,这个看以后会有什么感悟吧)。上下层相应的agent是父子关系,每个 agent都依赖它所有的上层agent。每个agent实现特定的功能,而且又由三个组件构成:Presentation,Abstraction和 Control。从这个意义上说,它和MVC确实有相似之处。presentation组件提供agent的可视行为,相当于 view,abstraction维护agent的数据,并提供对数据的操作接口。control组件联接presentation和 abstraction,并与其它的agent通信。

上午还捣鼓了一下Eclipse的GUI开发,因为王强问了一下,以前装了个visaul editor插件,一直没有用过,上午就趁机想试试,用Visual Class创建了一个类后,代码和frame都显示出来了,但是当我想向frame里添加控件时捣鼓了半天都找不到控件的窗口,郁闷,google了一把,找到一个帖子,终于把控件窗口弄出来了,为了避免忘记,现引用如下:
这个“控件面板”或“部件窗口”或“拖板”(没有统一称呼)在VE里称为 Palette 。如下打开:
window -> customize perspective -> shortcut标签 -> submenus选择 show view -> 单击General在右边 palette 打勾。
确定后 回到 window -> show view -> palette 。

2009年5月1日星期五

终于可以自由地在opera上用tor了

今天下午在网上无所事事,突然想起了洋葱头,我一直是用Firefox和Tor搭配使用的,因为FF有专门针对洋葱头的插件,这方面opera就差一些了。但是我很喜欢opera,所以在公司里一般的网上浏览都是用opera,而使用Google的服务,例如google calendar,docs,blog等等,就要用FF了,因为公司把Google的服务屏蔽了。

今天上网无所事事就又想到这个问题,上网搜了一下,opera是可以使用Tor的,打开Tor后,再设置opera的代理为:127.0.0.1,端口是8118,设置好后就可以用Tor了。不过这样还是有点不方便,因为要用的时候得到preference去设置代理,不用了还是要再设置一遍。

后来想能不能在opera的菜单或者工具栏里面添加按钮,实现enable和disable代理那就方便了。秉承知之为知之,不知google知的原则,搜索一把,网上有些帖子说明怎么在opera里添加菜单和按钮,考虑了一下,加菜单太麻烦,还有修改opera默认目录里的ini文件,搞不定啊。后来终于发现个好帖子,说有个网站:http://nontroppo.org/tools/buttonmaker,可以用来制作opera使用的按钮,填写几项就好了,比较重要的项是:Choose the button's action,就是点击这个按钮要执行的动作,我从commbo里面找了一下,有两个对得上号的操作:Enable proxy servers和Disable proxy servers。将相应的action选好,然后呢再设置一下button's title,这一项不是必须的,可填可不填。还有个选项是设置按钮的icon,我选了两个:一个是“Reload”,一个“stop”。这样设置完后,大功告成了,点击create button就会有相应的按钮被创建了,创建后会将你创建的按钮显示出来,点击这个按钮,opera就会把这个按钮添加到浏览器里了,在菜单工具->外观->按钮->我的按钮里面找到。

到了这一步基本就搞定了,然后在工具栏上点击右键,选择自定义,然后在我的按钮里找到我们加入的按钮,将按钮拖到工具栏上就行了。

2009年4月29日星期三

MVC模式初步

2.4节首先提到了interactive系统,这类系统使用图形化的UI来完成高度的交互,使用户能够方便地访问系统的服务。构建这类系统时,一个难点就是:将UI和系统的核心功能分离,因为系统的核心功能一般是保持稳定的,而系统的UI却时时变化,或者很可能有多套的UI。为此,对于这类 interactive系统,书中介绍两种模式:Model View Control模式和Presentation Abstraction control模式。

关于MVC模式的资料和文章很多,现在发现不同的文章里对MVC的介绍和解释各有不同,且按下不表吧,现在POSA 里是怎么说的。Model类,其职责是提供应用的核心功能,为独立的View和Control提供注册(对于这一点我以前是不知道,或者不明晰的,View和Control要在Model里面注册,我认为这么做的目的当然是为了让Model能够了解掌控View和Control的信息,以使得 Model能够将更新通知给两者,这里很自然地想到了Publish-Subscribe模式或者observer模式),当数据更新是通知注册的 View和control。View类,其职责是:创建和初始化相关的Control(Control和View联系较紧,因为不同的View有不同的输入处理方式,所以Control由View对象来创建。在书中的MVC实现步骤中还提到,虽然一个View只对应一个Control,但是对应的 Control是可以动态的变更的,例如在运行时,动态地更换View对应的Control,使得View对用户的输入作出不同的响应。),显示更新信息,从Model处获取数据。Control类,其职责是:接收用户输入,将事件转化为服务请求并发送给Model。

图一是MVC的类图

图二是GoF的observer模式的类图,可以比较一下,两个图是非常相似的。

图三是MVC模式各个类对象的初始化序列图,需要关注的是各个对象的创建过程。

2009年4月27日星期一

Broker模式

Broker模式用于分布式系统,对系统中的各个组件解耦,协作,通信等。服务在Broker中注册自己,使得client可以通过它们提供的接口访问自己,client向Broker发送请求以访问服务提供的功能,Broker根据请求定位合适的服务器,然后将请求发送给它,并将结果和异常等信息返回。

我的理解,这个Broker模式就好比生意场中的代理人或者中间人,他负责联系买卖双方,通风报信,促成生意。也可以这么理解,Broker是一个中间层,它封装了各个服务的实现细节,client不必(估计也不想)知道服务的细节,只要给Broker发送请求,剩下的事情由它去办,这就使得client和server的耦合很松,整个系统的扩展性有了大的提高。

Broker模式包括六种主要的组件:clients,servers,brokers,bridges,client-side proxies和server-side proxies。

server实现某些功能并通过接口向外提供之。server的责任包括:实现服务,在本地的broker中注册自己,通过server端的proxy向client发送响应和异常信息。server的协作者包括server端的proxy和broker。

client的责任包括:实现用户的功能,通过客户端的proxy向server发送请求。它的协作者包括客户端的proxy和broker。

在文中用messenger描述broker,它是client和server之间通信的桥梁,它将client的请求发送给server,也将server的回应和异常发送给client。因此,broker必须通过某种方式实现唯一的标识,来分辨和定位数据的来源和目的地。它提供接口使得client和server都能在它那儿注册。其实这很好理解,因为只要这样,broker才能统领全局,了解所有已经存在的client和server。前段时间看了Android,现在想起来,它很可能也使用的这个模式,Android工程里的AndroidManifest.xml的功能应该就是这样,所有在工程添加的类,无论是Activity,service,还是broadcast receiver等等,都必须在AndroidManifest.xml中注册,否则应用不能实现相关的功能。我推测Android里应该专门有一个模块来管理AndroidManifest.xml,每当一个应用被按照进系统后,这个模块就检查AndroidManifest.xml,收集信息,将里面的类,服务,过滤器等等信息都收集后保存,这样当有Intent(特别的隐式的Intent)被抛出后,系统才能够定位相应的Activity或者service,否则系统是找不到的。

client端的proxy是client和broker之间的中间层,它封装系统指定的功能,例如client和broker之间的具体通信机制,内存块的分配和删除,参数和结果的排列等,可以这么说,client的proxy是一个打包器,将client的请求进行封包。而与之相对的是server端的proxy,就是一个拆包器了,同时将server的结果和异常信息打包发给client。

Bridge是一个可选组件,在Broker系统里,很可能是一个异构的系统,存在多个Broker,各个Broker之间如果要通信,就需要使用Bridge,它在各个Broker之间找出路由。

Broker模式分为两类:一类是没有broker,client端的proxy和server端的proxy之间直接通信,不用broker来中转通信,这类broker模式的好处是性能高,因为省去了一道中转,但是在两端的proxy之间需要使用同样的数据格式或者协议来传输。另一类就是有broker,两端的proxy使用不同协议或者数据格式不要紧,由broker来统一。

2009年4月22日星期三

黑板模式及其实现

对于Blackboard模式,看完前面的介绍还是有点迷糊,书中一开始就提到了几个现实问题,例如图像,语音识别,这类问题当前虽有多种解决方案,但是 这些方案并不是非常成熟,也没有非常好的算法来实现。书中解释了为什么用人工智能和专家系统来解决这类问题的局限性,不过这段没怎么看懂。

黑板模式的思想是,有一系列独立的模块,或者说是方案,这些方案能解决部分问题的一部分,这些方案进行协作,使得问题问题能够最终解决。这就像一群人在一块 黑板前,共同解决一个问题,根据当前问题解决的程度和状态,不同的人上前到黑板上解决他所能解决的部分,这样经过多人的协作,最终能够将问题解决。这就是 黑板模式这个名字的来历。这里有个问题,就是根据当前的解决状态,下一个由谁上前去解决,是由个人决定的,因为个人知道该谁上前去,那么在软件中,前面提 到了,有多个方案,这些方案能解决问题的一部分,就相当于单个的人,那么当前由哪个方案去解决,由谁来调度,书中提出,黑板模式中有一个仲裁组件 (moderator component),它根据当前的状态来调度有哪个模块去解决。

关于黑板模式的实现,书中提到,将黑板模式的实 现分为三个主要的组件:Blackboard,Knowledge Source和Control。Blackboard可以看作一个容器,它存储数据,包括控制数据,状态数据等等。Blackboard为这些数据提供了 读写接口使得Knowledge Source可以访问它们;Knowledge Source的职责是评估自身的能力以及适合自身执行的环境和前提条件,因此Knowledge Source也可以分为两个小部分,一个是condition part,用于评估当前的状态,通俗的说就是对于某个问题,当前解决了多少,得到了什么中间结果,当前需要解决什么问题,这些条件是不是满足自身的触发条 件以使得自身可以运行,另一个是action part,用于执行相应的操作,得到相应的结果,同时也要保证Blackboard更新最新的结果;Control的职责是监视Blackboard的变 化,决定下一步的动作,它调度Knowledge Source评估当前的状态以决定下一步动作,这里有个策略的问题,可能遇到两种困难的情况:一种是根据当前的状态,找不到一个匹配的Knowledge Source来处理;另一种情况是当前的状态可以有多个Knowledge Source匹配。这里需要有一个策略以处理上两种情况。

最后要注意的一个问题是何时停止的问题,也就是出口,因为书中提到Control是一个loop,不停地查询Blackboard的数据和状态以调度,如果 这样下去就是一个死循环了,需要一个出口,得到什么结果时停止。这也是有Control控制的。下图说明三个组件的关系:

2009年4月21日星期二

Pipes and Filters pattern的概念和实现

对于the Pipes and Filters pattern,书中提到了几个概念:管道(Pipe),过滤器(Filter),数据源(Data source),数据渊(Data sink)。管道用于传输,缓冲和同步数据,它可以与数据渊,数据渊,过滤器协作;过滤器用于获得输入数据,执行相应的操作,输出数据,它与管道进行协作;数据源用于将待处理的输入数据传给管道,它与管道协作;数据渊用于收集输出数据,它与管道协作。

另一个概念就是主动和被动,管道,数据源,数据渊都有主动和被动之分,两者的区别,一个是主动的去获取或者输出数据(书中的pull和push),而被动的则是通过外界触发,例如事件,或者相应的协作对象调用来触发。

Pipes and Filters 的实现:
1,将系统任务分为一个顺序的执行状态。没个状态只依赖他的直接前驱的输出,也就是说这些执行状态是一个线性的结构,第n+1个状态只依赖于第n个状态的输出结果(直接前驱),而不是还依赖与其它的前驱。
2,定义在管道中传输的数据的格式。为数据定义一个统一的格式可以有效地提高可扩展性,因为这样一来,就可以通过组合不同的过滤器以实现不同的功能,因为数据的格式是统一的,所以各个过滤器的前后次序不必担心,如果数据的格式不统一,那么调整过滤器的顺序就要大费周章,因为过滤器输入输出的数据格式不一样,调整过滤器的顺序需要进行大量的修改。
3,决定如何实现管道的连接。可以通过直接调用的方式来实现管道的连接,例如前驱的过滤器直接调用后续的过滤器中的方法,这种管道的实现方式可以实现数据的传输,但是这种方式的缺点是不言而喻的,就是耦合性太强了,一旦需要调整过滤器的位置,那么大规模的修改是不可避免的。比较好的方式是利用操作系统提供的相关特性来实现管道,例如可以用Windows下的message queue等用于进程间通信的方式,在Linux下用管道来实现。
4,设计和实现过滤器。过滤器的实现要考虑前面提到的主动和被动方式,对于被动型的过滤器,可以用一个或者一组函数来实现即可;而对于主动型过滤器,应该考虑用线程或者进程来实现。
5,设计错误处理。

2009年4月20日星期一

层模式的实现及管道过滤器模式初步

层模式的实现步骤:
1,定义分层标准,也就是需要一个准则,用它能把待开发的系统抽象出各个层来。这个准则很重要, 因为它觉得了层的划分和粒度。书中提到一个大概的准则,从上到下依次:User visible elements;Specific application modules;Common services level;Operating system interface level;Operating system 和 Hardware。
2,为各层命名并分配任务。
3,为各层指定服务(我这里疑惑的 是服务和第二点的任务有什么区别?)。书中提到最重要的实现原则是各层要严格地隔离开,杜绝跨层的组件。某个层中的函数和接口,它们的返回值,参数,错误 类型等等,都应该是某种语言的内建类型(例如int,float等)或者是在改层中定义的类型,或者是一个公共的数据类型。但是书中的意思是,使用公共的 数据类型会影响各个层之间的界限和划分,所以应该要少用。
服务要多放在上层,下层要保持苗条,少放服务。上层使用下层的服务,下层为上层提供服务。
4,重新分层,这是一个迭代的过程,因为在初始的划分中难免有错误和不合理的地方,例如某一层会使用多个层的服务,而不是只使用它的下一层的服务,这是违反层模式原则的。
5,为每层指定接口,这个接口是为了上层能够使用该层的服务。对上层来说,下层最好是像一个黑盒,上层不知道下层的实现,但上层知道下层提供的服务并可以使用这些服务。
6,考虑每层的结构,传统上,很大部分的精力都放在如何在层与层间建立更好的关系,但是单个层内部的结构也很重要,如果某一层非常复杂,那么可以将该层分为多个组件,并对这些组件应用设计模式。
7, 指定层间的通信,书中提到推拉模型,第J层调用第J-1层的服务,将所需要的信息通过调用传给J-1层,是为推模型;低层将信息返回给高层,是为拉模型。 推拉模型可以用Publisher-Subscriber模式,或者Pipes and Filters 模式来实现。
8,对相邻的层解耦,前一步提到了指定层间的通信,用推拉模型,不过这可能会造成相邻两层耦合度比较高,书中提到可以用接口和回调函数降低耦合度(不过我还是不明白为什么用回调就能解偶,我觉得回调函数和Publisher-Subscriber模式没什么区别)。
9,建立错误处理机制。

层 模式的变体,可以称为松散的层模式,松散层模式中,高层不但可以使用下一层的服务,也可以使用其它低层的服务。这样的好处是效率和可塑性,但这是以牺牲可 维护性为代价的。一般在系统软件中使用这种松散的层模式比较多,例如Unix,X Window等,因为这些系统一般比较稳定,不需要经常做大的修改,它们对效率的追求也比较高。

the Pipes and Filters pattern:
为 说明该模式,书中举了一个例子,假设有一个新的语言,类似Java,要为该语言开发编译器,编译器的处理过程就是一个数据流的处理过程,首先输入的是 ASCII码的文本,也就是用该语言编写的源文件,经过扫描,得到句元流(Token stream),经过语法分析,得到抽象语法树(abstract syntax tree),经过语义分析,得到argument abstract syntax tree(不知道怎么翻译),再经过中间码生成,得到中间码(和Java的字节码一个意思),最后经过优化,得到可在
特定CPU上执行的机器码指令。如下 图所示:

2009年4月17日星期五

architectural pattern的分类

第二章主要讲述architectural pattern:“Architectural patterns express fundamental structural organization schemas for software systems. Architectural patterns represent the highest-level patterns in our pattern system. They help you to specify the fundamental structure of an application.”

这一章总共包括8个模式,这些模式又分为4类:
  • From Mud to Structure
我的理解,这类的模式主要的作用是结构化,避免大型的系统成为一盘散沙(mud和a 'sea' of components or objects),让系统条理清楚。这类的模式包括: Layers pattern,the Pipes
and Filters pattern,和Blackboard pattern

  • Distributed Systems
专门针对分布式的模式,包括一个模式Broker pattern,这类模式还和另外两个模式有关,Microkernel和Pipes and Filters。

  • Interactive Systems
用于交互系统,也就是内部交互比较多的系统吧,一般的UI和MMI系统都可以算作这类系统。包括两种模式Model-View-Controller pattern和Presentation-Abstraction-Control pattern。

  • Adaptable Systems
用于对扩展性要求很高的系统,随着技术的更新,需求的变化,系统需要不断地扩展。包括两个模式:Reflection pattern 和 the Microkernel pattern

先 从From Mud to Structure的几个模式谈起,Layer模式,是针对与那些可以被多个子功能块的系统,每个或者每组功能块可以被实现为一层,但是书中也提到了,对 于一个大型的软件系统来说,有时候有些功能块的界限并不明晰,不好区分。Layer模式是三种模式中使用最广泛的,很多大型的软件都是以层来架构的,特别 的网络方面的系统,基本上都是分层架构。

the Pipes and Filters pattern 主要针对出来流数据的系统,例如视频音频流等,每个处理步骤都被封装在一个过滤器对象中,流数据在各个过滤器中通过管道传输,组合不同的过滤器能得到不同 的结果。对这个模式,我以前不知道,当我读到这里时我就有些明白了,当年在听DirectDraw和DirectShow的presentation的时 候,对于流媒体的文件,MPEG4,Mp3等等,可以用相关的工具分析里面的过滤器及接口,如果想要自己做一个播放器,也要建立相关的过滤器和管道,想到 那次presentation的内容,再结合今天读到的这个模式,我就明白了,原来这也是一种架构模式啊,我估计很多媒体播放器就是用的这个架构。

至于Blackboard pattern,书中提到这是来源于人工智能,当你要开发的系统,它的需求并不明朗,位于一个全新的不成熟的领域,那么这个时候,可以用这个模式,先做一个次优的(suboptimal)方案,待到功能逐渐明晰和成熟后,再来修改。

2009年4月14日星期二

对三种模式的理解

昨天提到了在POSA中把模式分为三种,今天继续看,写一点对三种模式的理解。

architectural pattern:An architectural pattern expresses a fundamental structural organi-zation schema for software systems. It provides a set of predefined subsystems, pecifies their responsibilities, and includes rules and guidelines for organizing the relationships between them.
我的理解,architectural pattern为整个软件系统奠定了整体的框架和架构基础,软件系统的框架搭建好后,详细的实现就在这个框架下进行,比如整个软件系统分为几个模块,或者 说几个子系统,每个模块的责任是什么,各个模块之间的关系是什么,把这些厘清后,具体的细节就好实现了。MVC就可以看作是一种 architectural pattern。

design pattern:A design pattern provides a scheme for refining the subsystems or components of a software system, or the relationships between them. It describes a commonly-recumng structure of communicating components that solves a general design problem within a particular context.
我的理解,design pattern比architectural pattern要轻量级,POSA说“Design patterns are medium-scale patterns“,它主要用于模块或者子系统。

idioms:An idiom is a low-level pattern specific to a programming language. An idiom describes how to implement particular aspects of components or the relationships between them using the features of the given language.
我 的理解,这个idioms还不知道怎么翻译,按文中的意思,它是和特定的语音相关的,用特定的语言实现模块的部分功能或者模块间的关联。从这里看出,它比 design pattern又低了一级,它实现更细节的东西。书中举了个例子,就是引用计数,C++用引用计数来管理动态分配的资源,而smalltakl提供了 gc,所以不需要用这种方法。目前对这个idioms还没什么概念,在后面的学习中再领悟吧。

高级别的模式很可能要搭配使用低级别的模 式,书中举了一个例子,就是MVC,它是一个architectural模式,View是处理显示的,Control是处理输入的,Model是做业务处 理的,前两者和后者紧密联系,当业务处理有了新的结果,那么这个结果必须马上被显示处理,必须立即将新的结果通知View,当有用户输入发生时,也必须立 即通知Model进行业务处理。这种情况下,就可以使用design pattern里面的Obeserver模式,定好三者之间的subject和obeserver角色,就可以很好地解决这个问题。各个不同级别的模式经 常要混合使用以达到好的效果,所以Alexander说:Each pattern depends on the smaller patterns it contains and on the larger patterns in which it is contained.

2009年4月13日星期一

POSA的第一篇日记,何为模式

用processing,outpu和input来解释MVC很有点意思,processing,output,input分别对应Model,View和control。

模式可以从三个方面来考虑:context(场景或者背景,也可以理解为已知的先决条件);problem(可重现的问题,由前面的场景引发,需要解决什 么,要达到什么目的);solution(方案,一个已被用过的,或者被论证过的有效方案,解决特定场景的某类问题的方案,这个方案可能只是一个框架,这 个框架是成熟的,你可以对这个成熟的框架进行修改定制以更好地解决你的问题)。

POSA里将模式分为:architectural pattern,design pattern和idioms,这个GOF的分法有点区别,GOF分为creational,structural和behavioral。这个在以后的学习中慢慢体会吧。