项目业务背景:该项目是XX公司为YY能源公司创建的企业内容管理(ECM,Enterprise Content Manager)解决方案。该项目分为两大部分,CM和BPM,其中CM创建并保存YY公司在生产及运营过程中产生的文档,而BPM则负责处理YY公司中的业务流程。同时,该项目还与YY公司现有的企业系统EAM进行集成,将EAM系统中的一些文档ECM进行存储。
项目开发背景:ECM系统包含了三个子系统,分别是CM系统,BPM系统与COMMON系统。CM负责文档的创建与保管;BPM调用DM中的文档,进行业务处理,BPM在业务流程中也可能会产生一些文档到BPM中;COMMON为DM和BPM提供业务参数配置,也完成外部系统(EAM)与内部系统的交互工作。我是CM中的一名开发工程师,前期负责几个CM中几个模块的开发工作,后期主要负责与其它系统(如BPM、EAM等)的集成。
CM中独立模块的开发工作就不多说了,根据需求分析说明书与需求跟踪矩阵,还有业务状态切换图来确认系统中每个业务的用例,从而完成该功能的开发。难度不大,最重要的是理解业务。我们主要讲解在系统集成过程中发生的一些问题。
对每一个未接触过集成的人来说,都觉得系统集成是一个高深莫测的东西。因为他们只是在系统内部的框架内完成接口的调用,不懂得换了框架之后,接口的实现会发生什么样的变化。对框架的依赖性太强,这是现代开发人员的一个弊病,其实与外部系统的集成从底层看来,就是使用其它框架的实现。
在BPM与CM进行集成时,既可能有BPM调用CM,也可能有CM调用BPM,但是无论怎么调用,归根到底都是为对方提供可行性的接口。我们以BPM调用CM为例,在代码中讲述集成。在系统集成中,有一个很重要的安全问题,那就是单点登录,由于此部分内容较多,这里不做详细介绍。
我们知道Servlet通过HTTP协议,在Request获取数据时,有GET、POST、PUT几种方式,我们(CM)在为BPM提供接口时,就需要考虑这几种常用的情况,考虑到BPM对CM的调用比较频繁,以及系统的可扩展性,我们提供一个抽象类,来实现支持这几种方式。为保证数据的正确性,我们在里面使用事务进行封装。
public abstract class AbstractRestController {
private static final int POST = 0;
private static final int GET = 1;
private static final int PUT = 2;
//使用Log4j记录日志
protected static final Logger logger = Logger
.getLogger(AbstractRestController.class);
protected ThreadLocal<HttpServletRequest> request = new ThreadLocal<HttpServletRequest>();
protected String body;
private BackLoginModule module = null;
/**
* 使用注解处理POST请求
*/
@RequestMapping(method = RequestMethod.POST)
public void handlePOSTRequest(@RequestBody String body,
HttpServletRequest request, HttpServletResponse response)
throws ServletException {
this.body = body;
process(request, response, POST);
}
/**
* 使用注解处理GET请求
*/
@RequestMapping(method = RequestMethod.GET)
public void handleGetRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
process(request, response, GET);
}
/**
* 使用注解处理PUT请求
*/
@RequestMapping(method = RequestMethod.PUT)
public void handlePUTRequest(@RequestBody String body,
HttpServletRequest request, HttpServletResponse response)
throws ServletException {
this.body = body;
process(request, response, PUT);
}
private void process(HttpServletRequest request,
HttpServletResponse response, int type) {
this.request.set(request);
//使用事务,发生错误时可以回滚
UserTransaction tx = null;
try {
tx = TransactionUtil.getUserTransaction();
tx.begin();
Object result = null;
if(type == POST)
result = processPost();
else if(type == PUT)
result = processPut();
else if(type == GET)
result = processGet();
writeResponse(result, response);
tx.commit();
} catch (EcmException ex) {
try {
tx.rollback();
} catch (Exception e) {
e.printStackTrace();
}
logger.error("Error occurred when invoking remoting services.", ex);
BizResponse br = new BizResponse();
br.setErrorCode(ex.getCode());
br.setErrorDescription(ex.getErrorDescrption());
Map<String, String> map = new HashMap<String, String>();
map.put(RestConstants.RESPONSE_BODY_CONTENT, body);
br.setItem(map);
writeResponse(br, response);
} catch (Exception ex) {
try {
tx.rollback();
} catch (Exception e) {
e.printStackTrace();
}
logger.error("Error occurred when invoking remoting services.", ex);
BizResponse br = new BizResponse();
br.setErrorCode(ErrorCode.UNKNOW_ERROR);
br.setErrorDescription(ErrorMessageHelper
.getErrMessage(ErrorCode.UNKNOW_ERROR));
Map<String, String> map = new HashMap<String, String>();
map.put(RestConstants.RESPONSE_BODY_CONTENT, body);
br.setItem(map);
writeResponse(br, response);
} finally {
this.request.set(null);
if(module != null){
try {
module.logout();
} catch (Exception e) {
// TODO: handle exception
}
}
// if (EcmConfig.useTomcat())
if (EcmConfig.useSSO == false) {
UserContextUtils.popSubject();
}
// SubjectUtil.clear();
ObjectStoreHelper.clear();
}
}
protected Object processPost() throws EcmException {
return null;
}
protected Object processGet() throws EcmException {
return null;
}
protected Object processPut() throws EcmException {
return null;
}
protected void writeResponse(Object bmpResp, HttpServletResponse response) {
String body = null;
if (bmpResp instanceof String) {
body = (String) bmpResp;
} else {
body = JsonUtil.toJson(bmpResp);
}
response.setContentType("application/json");
try {
PrintWriter out = response.getWriter();
out.print(body);
} catch (IOException ioe) {
logger.error("Error occurred when setting HTTP response via JSON.",
ioe);
}
}
protected HttpServletRequest getRequest(){
return request.get();
}
}
CM与BPM继承,并为提供接口时,新的接口(类)继承AbstractRestController抽象类,就可以通过几种不同的HTTP请求来进行数据传递,同时也保证了事务性。下面是为BPM提供的一个借阅接口,在这里我们使用了PUT方式。
@Controller
@RequestMapping("/borrowProcessUpdate.do")
public class UpdateBorrowStatusController extends AbstractRestController {
//此处使用PUT方式
@Override
public Object processPut() throws EcmException {
UpdateBorrowReq req = JsonUtil.fromJson(body, UpdateBorrowReq.class);
BorrowDocument[] borrowDocumentList = new BorrowDocument[req.getBorrowDocumentList().length];
//...此处省去部分代码
boolean flag = IntegrateBorrowProcess.executeProcess(obj,borrowDocumentList);
ResetUniversalResponse results = new ResetUniversalResponse();
results.setBsuccessed(flag);
return results;
}
其实,集成并不是像我们想象中的那么可怕,重要的是要找到它的突破口。我们这里所讲的集成,最重要的是AbstractRestController,它为外部提供了几种方式HTTP请求方式,给了BPM一个入口。