无论用户信息是否储存在目录服务中或XML文件中,OpePTK都允许开发人员为用户置备提供统一的API。通过使用OpePTK,开发人员将不必深入到每个用户信息存储库,而是集中利用API定义完善的集合以便与已知几种用户存储库的用户置备框架进行交互。此后,如果所要求的用户存储不被OpePTK支持,开发人员可以实现所要求的接口集合以便为框架提供与新用户存储库通信的通道。除了利用像修改密码、重置密码和密码恢复此类密码管理功能之外,通过使用OpePTK,开发人员可以在用户存储库上执行CRUD操作。OpePTK的当前版本支持作为用户信息存储库的LDAP(SPML)、JDBC和SuIdetityMaager。
OpePTK为框架用户提供一些前端设备,以便轻松访问引导与用户存储库交互的框架功能。当前所提供的前端包括JSR-168portlet、JAX-RPSWeb 服务端点和JSP标记库,除此之外还包括为框架直接交互的API。
OpePTK架构正如前面所述,OpePTK具备完善的架构,且具有易于扩展和易于定制的基类和接口;从开发人员的角度来看,OpePTK项目的目标是统一用户置备并使用用户信息存储库处理各种交互机制。以下是来自OpePTK的三个顺序层:
OpePTK使用层OpePTK服务层OpePTK核心框架位于使用层的各个组件的目的是减轻针对不同应用程序和客户机对框架功能的访问。位于该层的各个组件不会实现特定接口或遵循由框架定义的一些规则;相反,在不涉及所提供的JavaAPI的情况下,设计并实现它们旨在减少需要与OpePTK核心框架交互的不同应用程序的开发。
OpePTK标记库、OpePTKweb服务端点以及OpePTKJSR168portlet等当前组件可以实现减轻需要用户置备的应用程序开发。除了带有直接访问OpePTKAPI的Java应用程序,web应用程序、门户系统以及基于web服务的应用程序(需要具有对于终端用户或系统管理员可用的用户置备)可以从当前使用层组件中获益。上述每个组件完全支持所述用户置备功能,并内部使用OpePTKJavaAPI执行这些任务。作为框架与使用方发生交互点的OpePTKJavaAPI位于org.opeptk.provisio.*包内。
OpePTK服务层包含与不同类型用户存储库(像JDBC或LDAP)发生通信的各个组件。该层通常是开发人员关注的焦点,目的是增添对用户信息存储库新类型的支持。您可以简单地通过开发一种新服务为用户信息存储库的其他类型(像包含用户信息的XML层)添加支持,并且当需要时OpePTK将接管与新服务的通信。OpePTK使用描述所有可用服务的配置文件以及向服务提供所需配置参数的其他详细信息。通常,每种服务具有两部分;第一部分负责执行CRUD操作。这部分必须扩展抽象类org.opeptk.provisio.spi.Service并实现org.opeptk.provisio.spi.ServiceIF。提供这些类要求您编写向存储库插入、删除和编辑用户的方法。
有时使用方必须通过用户存储库找到一个用户或一组用户,因此框架必须具备查询后端的标准方式,由于所有后端没有相同的查询机制,所以需要一个转换器将框架标准查询转换为特定于后端的查询。OpePTK提供了接口和抽象类,当被实现时允许框架以统一的方式查询所有用户存储库。查询转换器应扩展org.opeptk.provisio.spi.QueryCoverter抽象类,且必须实现org.opeptk.provisio.spi.QueryCoverterIF接口,以便当用户或服务需要执行查询时允许框架加载该接口。
OpePTK核心框架的责任是桥接使用方和服务层。为了执行该项任务,核心框架需要使用它应该使用的适当上下文配置。针对用户置备而使用OpePTK的第一步是加载配置文件。配置文件包含对服务、上下文、对象、记录程序等的描述。上下文是我们用来互连各个特定服务、对象、记录程序的元素,且对用户存储库的访问将遍历我们选择的上下文。
图1简要地演示了OpePTK架构。
OpePTK用例作为IT系统的使能器和加速器,置备用于:
VoIP服务提供方ERP系统提供方互联网服务提供方定制应用程序开发遗留应用程序集成使用置备子系统可以减少开发人员需要花费在用户管理方面的精力。通常我们在企业间已经拥有几个身份存储库,并且用户置备的良好实现使得开发人员掌握身份管理的中心点,从而减少编程错误或用户错误的风险。拥有了用户身份管理的中心点,您可以在单点中针对用户管理应用所有规则,而不是在企业范围系统的几个部分中应用所有规则。
扩展OpePTK正如您已经看到的情况,可以通过在服务层中添加新服务扩展OpePTK以便支持新的用户存储库。在查看扩展的OpePTK前,我们应了解如何使用它。以下示例代码显示如何创建新的用户。配置文件名称是opeptk.xml,我们用于与用户存储库交互的上下文被称作sample-xml-store-cotext。示例代码片断将用户信息存储到存储库,而无需了解是那个存储库或其具备的结构种类。存储库由我们使用的上下文决定,因此更改我们使用的上下文可以更改我们所交互的存储库。
try{Cofiguratiocof=ewCofiguratio("opeptk.xml");SubjectIFsubject=cof.getCotextSubject("sample-xml-store-cotext");Iputiput=ewIput();Outputoutput=ull;iput.addAttribute("userid","Jack@ctu.com");iput.addAttribute("firstame","Jack");iput.addAttribute("lastame","Bauer");iput.addAttribute("password","mypassword");output=subject.doCreate(iput);}catch(ProvisioExceptioex){System.out.pritl("Operatiofailed"+ex.getMessage());}输入和输出是使用方通常用来向服务发送所需数据或从服务获取结果的两个类。但是框架核心将添加更多信息以便允许服务有效执行所请求的操作。正如您能看到的,在我们执行任何操作前,我们将添加包含前面提及的所有关于配置信息的配置文件。配置文件的默认名称为opeptk.xml;在我们调用配置的无带参数构造器时加载框架。在第二行中我们试图使用称作sample-xml-store-cotext的上下文。sample-xml-store-cotext的说明如下:
<Cotextid="sample-xml-store-cotext"><Subjectid="Perso"/><Serviceid="xml-store"><Properties><Propertyame="filepath"value="/opt/opeptk-sample/storage.xml"/></Properties></Service><Querytype="EQ"ame="userid"value="10459845"/></Cotext>该上下文在上下文内部被定义,其中包含几个上下文标记。正如您能看到的,上下文使用在xml-storeID下定义的上下文。当框架初始化服务时,上下文挑选所定义的属性。每个上下文可以根据需要初始化许多属性。例如,JDBC服务可以具有jdbcurl、userame、password、driver-class等属性。最后当我们使用查询无参数构造器时可以决定使用的默认查询类型。其他两个属性定义用于查询的对象属性和在候选实体中该属性的值。上述代码片断显示如何向上下文分配上下文,而上下文本身的定义如下所示:
<Serviceid="xml-store"classame="org.opeptk.provisio.spi.XmlStore"descriptio="AsampleServiceformaagigXMLidetitystorage"sort="userid"><Properties><Propertyame="filepath"value="/opt/opeptk-sample/storage.xml"/></Properties><Operatios><Operatiotype="create"/><Operatiotype="read"/><Operatiotype="update"/><Operatiotype="delete"/><Operatiotype="search"/></Operatios><Attributes><Attributeid="userid"serviceame="userid"/><Attributeid="firstame"serviceame="giveName"/><Attributeid="lastame"serviceame="lastame"required="true"/><Attributeid="password"serviceame="password"required="true"/></Attributes></Service>该定义可以包括具有默认值的所需属性、实现和支持服务的操作、在Subject属性之间的映射以及具有必要约束的等效Cotext属性。Cotext属性名称的每个属性值将存储在身份信息存储库中的名称下。通过使用映射机制,我们可以将我们在使用层内使用的属性名称与从后端用于存储属性值的真实属性名称分开。
所定义的上下文使用具有惟一ID称作Perso的对象。我们在Subject标记中定义的内容反映每个对象应具有的属性,这些属性应是如何处理的(强制性的、可选的、可能具有的约束性、类型等等)、这些属性如何被传递到CRUD操作以及它们如何被转换等等。下列示例代码显示了对象是如何被定义的。出于简单,该对象只约束一个属性。
<Subjectid="Perso"key="userid"password="password"classame="org.opeptk.provisio.api.Perso"><Attributes><Attributeid="fullame"><Trasformatios><Trasformtype="toService"useexistig="true"classame="org.opeptk.provisio.trasform.CocatStrigs"><Operatios><Operatiotype="create"/><Operatiotype="update"/></Operatios><Argumets><Argumetame="arg1"arg="attribute"value="firstame"/><Argumetame="arg2"arg="literal"value=""/><Argumetame="arg3"arg="attribute"value="lastame"/></Argumets></Trasform><Trasformtype="toFramework"useexistig="true"classame="org.opeptk.provisio.trasform.CocatStrigs"><Operatios><Operatiotype="read"/><Operatiotype="search"/></Operatios><Argumets><Argumetame="arg1"arg="attribute"value="firstame"/><Argumetame="arg2"arg="literal"value=""/><Argumetame="arg3"arg="attribute"value="lastame"/></Argumets></Trasform></Trasformatios></Attribute></Attributes></Subject>首先,我们已经拥有一个对象,它带有称作userid的惟一标识符属性、password属性和classame。key属性是一个可以与定义fullame的相同方式定义的属性。classame属性指向在扩展org.opeptk.provisio.api.Subject和实现org.opeptk.provisio.api.SubjectIF完全合格类的名称。应用该选项来使用自定义对象类使我们对执行对象的CRUD操作更好地控制。fullame属性由firstame空格字符和lastame组成。转换定义可以帮助我们在向上下文发送属性前,或者在从服务提取该属性时向框架传递属性前通过对属性执行自定义转换获得当前属性。在OpePTK中已经存在几种默认的转换,例如org.opeptk.provisio.trasform.CocatStrig。OpePTK转换类应扩展org.opeptk.provisio.trasform.Trasformatio并实现org.opeptk.provisio.trasform.TrasformatioIF。在org.opeptk.provisio.trasform.TrasformatioIF.trasform(...)方法内部,由于参数可以通过参数名称与值之间的映射进行访问,因此每种转换都可以获得任意数量的参数。
现在应该查看一下org.opeptk.provisio.spi.Service抽象类和org.opeptk.provisio.spi.ServiceIF接口,它们是每个上下文类的直接父类。
您在阅读本文过程中可能会问“我们为什么针对OpePTK的所有所述部分扩展抽象类且实现接口?”原因是在接口中存在几种方法,且所有这些方法通常在不同的扩展之间具有相同的实现方法。因此OpePTK开发人员决定将那些不同的方法放入到抽象类中,并决定他们是否更改那些通常相同的功能。在每项服务中应被实现的重要方法如下所示:
voiddoCreate(RequestIFreq,ResposeIFres)voiddoRead(RequestIFreq,ResposeIFres)voiddoUpdate(RequestIFreq,ResposeIFres)voiddoDelete(RequestIFreq,ResposeIFres)voiddoSearch(RequestIFreq,ResposeIFres)voiddoPasswordChage(RequestIFreq,ResposeIFres)voiddoPasswordReset(RequestIFreq,ResposeIFres)voidstartup()voidshutdow()除启动功能外,方法名称说明了每种方法的预期功能,其中,我们应初始化我们在服务期内使用的资源,例如数据库连接等。类似地,停止功能在类符合垃圾收集条件前将执行清除操作。
在下列示例代码中,我们假设拥有与下列XML文档类似的示例用户存储库。
<persos><perso><userid>Jack@ctu.com</userid><ame>Jack</ame><lastame>Bauer</lastame><password>sample_pass</password></perso></persos>下列doRead和doCreate的示例实现显示了如何使用RequestIF和ResposeIF参数。
@OverridepublicvoiddoRead(RequestIFrequest,ResposeIFrespose)throwsServiceExceptio{try{StrigkeyFw=this.getCotext().getDefiitio().getKey();StrigkeySrvc=this.getSrvcName(keyFw);StrigxpathStrig;StrigkeyValue=request.getSubject().getUiqueId();List<Compoet>attributes=ewLikedList<Compoet>();Strig[]attributeNames={"userid","giveame","lastame","password"};//attributeIDsusedbyrepositoryStrigUNIQIE_ID="userid";Compoetcompt;if(keyValue!=ull&&keyValue.legth()>0){if(keySrvc!=ull&&keySrvc.legth()>0){xpathStrig="//perso[@"+keySrvc+"="+"'"+keyValue+"']";respose.setUiqueId(keyValue);}else{respose.setStatus("UiqueIdattributeameisotset");retur;}}else{respose.setStatus("UiqueIdvalueisotset");retur;}this.getProperty("filepath");DocumetBuilderFactorydbf=DocumetBuilderFactory.ewIstace();DocumetBuilderdb=dbf.ewDocumetBuilder();/*Wehaveaccesstoallpropertiesthatwedefiediopeptk.xmlTheseattributesletushaveaccesstocofiguratioparametersthatServiceeedtooperatecorrectly.*/XPathxpath=XPathFactory.ewIstace().ewXPath();Documetpersos=db.parse(this.getProperty("filepath"));Nodeperso=(Node)xpath.evaluate(xpathStrig,persos,XPathCostats.NODE);/*Nowwehavethepersowithallofitsattributes.wecasedtheattributesbackbyusigtheresposeobject.howeverwecachecktherequestobjecttoseewhichattributesarerequestedadtheolysedbacktheattributesthatarerequested.*/for(iti=0;i<attributeNames.legth;i++){StrigattributeName=attributeNames[i];StrigattrXPath="/"+attributeName+"/text()";compt=ewCompoet();StrigodeValue=(Strig)xpath.evaluate(attrXPath,perso,XPathCostats.STRING);if(UNIQIE_ID.equals(this.getSrvcName(attributeName))){//wearedealigwithuiqueIDcompt.setUiqueId(attributeName);}else{/*otherattributes,weeedtosedbackattributeswiththierattributesidas*defiediopeptk.xmlcofiguratiofile,itiswhatgetFwNamedo.*/BasicAttrattr=ewBasicAttr(this.getFwName(attributeName),odeValue);compt.setAttribute(this.getFwName(attributeName),attr);}attributes.add(compt);//addigcompoettothelistofattributes}respose.setResults(attributes);respose.setStatus("SearchComplete");retur;}catch(Exceptioex){//Hadletheexceptios...}}doCreate()是在存储库中创建用户的方法,可能类似于:
@OverridepublicvoiddoCreate(RequestIFrequest,ResposeIFrespose)throwsServiceExceptio{PropertiesattribValues=ewProperties();Map<Strig,AttrIF>attributes=request.getSubject().getAttributes();Iterator<AttrIF>attNames=attributes.values().iterator();while(attNames.hasNext()){AttrIFattrib=attNames.ext();if(attrib!=ull){attribValues.put(attrib.getServiceName(),attrib.getValue());}}/*Asyousawwegetserviceameofeachattributeiordertomakesurethatattribute*willbesavedwiththeservicedepedetame.Herewehavealistofallattributesadtheirvalues,justformtherequiredstructureadisertititothestorage*/respose.setState(ResposeIF.STATE_SUCCESS);respose.setStatus("Createoperatiocomplete");/*Wecasedbacksomeattributestotheframeworkwhewefiishthecreatigthesubject,forexamplewemayreturbackasequeceumberidicatigouruserautogeeratedID.*/Compoetcopt=ewCompoet();BasicAttrattr=ewBasicAttr("sequeceID","database_retured_ID");copt.setAttribute("sequeceID",attr);List<Compoet>resultList=ewArrayList<Compoet>(1);resultList.add(copt);respose.setResults(resultList);retur;}最后,我们需要实现某一查询机制使框架在身份存储库的顶端执行典型查询。正如前面所述,通常只有一种实现的方法;该方法应以满足我们在配置文件中定义的查询类型的方式实现。目前存在10多种查询方式,分为简单查询和复杂查询两类。在第一种情况下,简单查询操作数可以是任何一种Boolea操作符;例如:like、begiwith、edwith、equal、otequal等等。复杂的查询是两个简单查询的结合或通过ad或or操作数实现的复杂查询。equal查询类型的示例实现如下所示:
@OverridepublicObjectcovert()throwsQueryExceptio{StrigBufferbuf=ewStrigBuffer();Strigame=ull;ittype=0;type=query.getType();if(type==Query.TYPE_AND||type==Query.TYPE_OR){//COMPLEXQUERY,Wewavethemforsakeofsimplicity}else{//Simplequeryame=query.getServiceName();//Dowehaveadefaultquery?if(ame==ull||ame.legth()<1){ame=query.getName();}switch(type){caseQuery.TYPE_EQUALS:{buf.apped("//perso[@"+ame+"='"+query.getValue()+"']");break;}/*GeerallythewaytoimplemetotherquerytypesissimilartoQuery.TYPE_EQUALSwithsomechagesregardigthelogicofselectigodes.*/caseQuery.TYPE_BEGINSWITH:caseQuery.TYPE_CONTAINS:caseQuery.TYPE_ENDSWITH:caseQuery.TYPE_GREATER:caseQuery.TYPE_GREATER_EQ:caseQuery.TYPE_LESS:caseQuery.TYPE_LESS_EQ:caseQuery.TYPE_NOTEQUALS:caseQuery.TYPE_SOUNDSLIKE:}}returbuf.toStrig();}结束语如您所见,针对OpePTK的新服务实现在日常项目中就像编写非常普通的代码段一样轻松。OpePTK具备设计良好的架构,从而支持在框架中添加任何种类的扩展。
如上所述,OpePTK使用层具有一些像置备portlet和JAX-RPCweb服务这样的组件,它们构建在OpePTK的使用方JavaAPI顶端。您必须具备与来自应用程序的OpePTK核心进行通信的其他方法;例如,您可以在OpePTKJavaAPI顶端开发REST端点,以便使REST友好应用程序执行用户置备操作。第一个示例演示了如何从REST端点访问OpePTK代码以便执行任何用户置备。









评论