Activiti Explorer 源碼淺析

概述

注意:本文中 Activiti 的版本爲 5.22,爲 5.X 系列的最後一個 RELEASE 版本。目前 Activiti 已經發展到了 7.X 版本,爲啥還用 5.X 版本,仍是存量項目的緣由。javascript

咱們的需求是將 Activiti 官網的 Demo 的流程設計器功能整合到咱們項目中,主要須要的功能是 Web 流程設計器 —— Activiti Modeler。Web 流程設計器的查找和保存功能是使用了 RestFul API 調用的,將源碼中的 editor-app 引入再定製化改造便可。除了查找和保存以外,咱們還須要查詢流程圖列表、新建流程圖、刪除流程圖等功能,但是發現這部分並無使用 RestFul API,這幾個功能是在後臺實現的,須要閱讀源碼查看這幾個功能如何實現。css

在閱讀源碼發現 Activiti 在前端 Demo 部分仍是比較混亂,先後端嚴重耦合,如今理清以下:html

  • Activiti Webapp Explorer:指的是官方的 Demo Webapp 工程
  • Activiti Explorer:主要使用 vaadin 來渲染 Activiti Webapp Explorer 中的頁面
  • Activiti Modeler: Web 端流程設計器,後端項目只有 3 個類提供 RestFul 接口,而前端項目則放到了Activiti Webapp Explorer 工程中,使用的是 AngularJS,Activiti Modeler 在 Activiti@5.17 版本由 Alfresco Activiti Enterprise 捐贈。
  • Diagram Viewer: Activiti 官方在 5.12 版本中添加的新組件,以 Raphaël 爲基礎庫,用 REST 方式(從 Activiti - Diagram - REST 模塊)獲取 JSON 數據生成流程圖並把流程的處理過程用不一樣的顏色加以標註。

Activiti Webapp Explorer

Activiti Explorer的 Webapp 工程在源碼中的位置爲 modules/activiti-webapp-explorer2/src/main/webapp,這個工程是官方Demo的核心工程。前端

比較奇怪的是這個 module 在 pom 文件中的 name 爲 Activiti - Webapp - Explorer V2,而整個工程並無 V1 版本,並且這個 module 沒有放到 activiti-explorer 中,而是獨立開來,在 pom.xml 文件中依賴了 activiti-explorerjava


主要的 activiti 相關依賴:

  • activiti-engine
  • activiti-spring
  • activiti-explorer
  • activiti-modeler
  • activiti-diagram-rest
  • activiti-simple-workflow

查看 web.xml 文件:

Listener

首先添加了一個 WebConfigurer 的 listener 用來加載 Spring context:android

<!-- To load the Spring context -->
<listener>
	<listener-class>org.activiti.explorer.servlet.WebConfigurer</listener-class>
</listener>
複製代碼

WebConfigurer 中配置了一個 mapping 用來相應相關的 REST 請求:git

...
dispatcherServlet.addMapping("/service/*");
...
複製代碼

第二個 listener 用來支持 Spring Beans 的 session-scoped 做用域:angularjs

<!-- To allow session-scoped beans in Spring -->
<listener>
	<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
複製代碼

Filter

定義了兩個 filtergithub

第一個 filter 爲 ExplorerFilterweb

<filter>
	<filter-name>UIFilter</filter-name>
	<filter-class>org.activiti.explorer.filter.ExplorerFilter</filter-class>
</filter>
...
<filter-mapping>
	<filter-name>UIFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
複製代碼

查看 ExplorerFilter 類主要作了以下事情:

判斷請求地址是否以 "/ui""/VAADIN""/modeler.html""/editor-app""/service""/diagram-viewer"開頭:

  • 若否,則將請求地址添加 "/ui"
  • 如果,判斷是否以 "/service" 開頭
    • 如果,判斷用戶是否登錄,沒登陸報錯,登錄則繼續
    • 若否,則繼續

第二個 filter 爲 JSONPFilter, 對 "/service" 開頭的請求進行過濾,處理 jsonp 的請求:

<filter>
    <filter-name>JSONPFilter</filter-name>
    <filter-class>org.activiti.explorer.servlet.JsonpCallbackFilter</filter-class>
</filter>
...
<filter-mapping>
    <filter-name>JSONPFilter</filter-name>
    <url-pattern>/service/*</url-pattern>
</filter-mapping>
複製代碼

servlet

定義了名爲 Vaadin Application Servlet 的 servlet,響應 "/ui""/VAADIN" 開頭的請求:

<servlet>
	<servlet-name>Vaadin Application Servlet</servlet-name>
	<servlet-class>org.activiti.explorer.servlet.ExplorerApplicationServlet</servlet-class>
	<init-param>
		<param-name>widgetset</param-name>
		<param-value>org.activiti.explorer.CustomWidgetset</param-value>
	</init-param>
</servlet>

<servlet-mapping>
	<servlet-name>Vaadin Application Servlet</servlet-name>
	<url-pattern>/ui/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
	<servlet-name>Vaadin Application Servlet</servlet-name>
	<url-pattern>/VAADIN/*</url-pattern>
</servlet-mapping>
複製代碼

查看 ExplorerApplicationServlet 發現出現了一大段用java拼html片斷的代碼,主要做用是渲染根據瀏覽器類型渲染不一樣的css:

@Override
  protected void writeAjaxPageHtmlVaadinScripts(Window window, String themeName, Application application, BufferedWriter page, String appUrl, String themeUri,
          String appId, HttpServletRequest request) throws ServletException, IOException {
    super.writeAjaxPageHtmlVaadinScripts(window, themeName, application, page, appUrl, themeUri, appId, request);
    
    String browserDependentCss = "<script type=\"text/javascript\">//<![CDATA[" +
      "var mobi = ['opera', 'iemobile', 'webos', 'android', 'blackberry', 'ipad', 'safari'];" +
      "var midp = ['blackberry', 'symbian'];" +
      "var ua = navigator.userAgent.toLowerCase();" +
      "if ((ua.indexOf('midp') != -1) || (ua.indexOf('mobi') != -1) || ((ua.indexOf('ppc') != -1) && (ua.indexOf('mac') == -1)) || (ua.indexOf('webos') != -1)) {" +
      " document.write('<link rel=\"stylesheet\" href=\"" + themeUri +"/allmobile.css\" type=\"text/css\" media=\"all\"/>');" +
      " if (ua.indexOf('midp') != -1) {" +
      " for (var i = 0; i < midp.length; i++) {" +
      " if (ua.indexOf(midp[i]) != -1) {" +
      " document.write('<link rel=\"stylesheet\" href=\"" + themeUri +"' + midp[i] + '.css\" type=\"text/css\"/>');" +
      " }" +
      " }"+
      " }" +
     " else {"+
     " if ((ua.indexOf('mobi') != -1) || (ua.indexOf('ppc') != -1) || (ua.indexOf('webos') != -1)) {" +
     " for (var i = 0; i < mobi.length; i++) {" +
     " if (ua.indexOf(mobi[i]) != -1) {" +
     " if ((mobi[i].indexOf('blackberry') != -1) && (ua.indexOf('6.0') != -1)) {" +
     " document.write('<link rel=\"stylesheet\" href=\"" + themeUri + "' + mobi[i] + '6.0.css\" type=\"text/css\"/>');" +
     " }" +
     " else {" +
     " document.write('<link rel=\"stylesheet\" href=\"" + themeUri + "' + mobi[i] + '.css\" type=\"text/css\"/>');" +
     " }" +
     " break;" +
     " }" +
     " }" +
     " }" +
     " }" +
     " }" +
     "if ((navigator.userAgent.indexOf('iPhone') != -1) || (navigator.userAgent.indexOf('iPad') != -1)) {" +
     " document.write('<meta name=\"viewport\" content=\"width=device-width\" />');" +
    "}" +
    " //]]>" +
    "</script>" +
    "<!--[if lt IE 7]><link rel=\"stylesheet\" type=\"text/css\" href=\"" + themeUri + "/lt7.css\" /><![endif]-->";
    
    page.write(browserDependentCss);
  }
複製代碼

從下面代碼能夠看出,整個前端頁面由ExplorerApp 來渲染:

@Override
  protected Class< ? extends Application> getApplicationClass() throws ClassNotFoundException {
    return ExplorerApp.class;
  }

  @Override
  protected Application getNewApplication(HttpServletRequest request) {
    return (Application) applicationContext.getBean(ExplorerApp.class);
  }
複製代碼

至此,終於找到了Activiti App的頁面入口 ExplorerApp

Activiti App的頁面入口 ExplorerApp

初始化顯示Login頁面:

public void init() {
    setMainWindow(mainWindow);
    mainWindow.showLoginPage();
  }
複製代碼

頁面與類的對應關係:

  • 登錄頁面:LoginPage
  • 主頁面佈局:MainLayout
  • 已部署流程定義:ProcessDefinitionPage
  • 流程設計工做區:EditorProcessDefinitionPage

已部署流程定義:ProcessDefinitionPage

主要關注 4 個功能的代碼:

  • 查詢已部署的流程定義的列表
  • 啓動流程
  • 轉換爲可編輯模型?(有一些是禁用的)
  • 根據流程定義ID查詢流程定義信息

一、查詢已部署的流程定義的列表

主要查詢的代碼以下:

// DefaultProcessDefinitionFilter.java
  ...
  public ProcessDefinitionQuery getQuery(RepositoryService repositoryService) {
    return getBaseQuery(repositoryService)
            .orderByProcessDefinitionName().asc()
            .orderByProcessDefinitionKey().asc(); // name is not unique, so we add the order on key (so we can use it in the comparsion of ProcessDefinitionListItem)
  }
  ...
  protected ProcessDefinitionQuery getBaseQuery(RepositoryService repositoryService) {
    return repositoryService
            .createProcessDefinitionQuery()
            .latestVersion()
            .active();
  }
  ...
複製代碼

獲取到了 ProcessDefinitionQuery 就能夠獲取 ProcessDefinition 列表:

List<ProcessDefinition> processDefinitions = processDefinitionQuery.listPage(start, count);

複製代碼

processDefinition 的結構以下:

image


二、啓動流程

須要注意的是,在啓動流程還增長了一個判斷 process-definition 是否認義了一個 start-form,因爲在咱們實際使用中不會使用到 start-form,因此咱們在改寫這塊功能的時候增長一個可否發佈的判斷:是否存在 start-form,若存在,則不能啓動;後面的啓動後判斷當前用戶是否存在該流程的任務功能能夠忽略。

// StartProcessInstanceClickListener.java

public void buttonClick(ClickEvent event) {
    // Check if process-definition defines a start-form
    
    StartFormData startFormData = formService.getStartFormData(processDefinition.getId());
    if(startFormData != null && ((startFormData.getFormProperties() != null && !startFormData.getFormProperties().isEmpty()) || startFormData.getFormKey() != null)) {
      parentPage.showStartForm(processDefinition, startFormData);
    } else {
      // Just start the process-instance since it has no form.
      // TODO: Error handling
      ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId());
      
      // Show notification of success
      notificationManager.showInformationNotification(Messages.PROCESS_STARTED_NOTIFICATION, getProcessDisplayName(processDefinition));
      
      // Switch to inbox page in case a task of this process was created
      List<Task> loggedInUsersTasks = taskService.createTaskQuery()
        .taskAssignee(ExplorerApp.get().getLoggedInUser().getId())
        .processInstanceId(processInstance.getId())
        .list();
      if (!loggedInUsersTasks.isEmpty()) {
        ExplorerApp.get().getViewManager().showInboxPage(loggedInUsersTasks.get(0).getId());
      }
    }
  }

複製代碼

TODO:判斷是否可以啓動流程邏輯:

  • 已經啓動過的流程不能再啓動;
  • 存在start-form,若存在不能啓動

三、轉換爲可編輯模型

可否轉換爲可編輯模塊邏輯:

// ProcessDefinitionDetailPanel.java

    if(((ProcessDefinitionEntity) processDefinition).isGraphicalNotationDefined() == false) {
      editProcessDefinitionButton.setEnabled(false);
    }

複製代碼

須要注意的是轉換須要彈窗讓用戶二次確認

轉換邏輯:

// ConvertProcessDefinitionPopupWindow.java

        try {
          InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
          XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
          InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
          XMLStreamReader xtr = xif.createXMLStreamReader(in);
          BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
          
          if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {
            notificationManager.showErrorNotification(Messages.MODEL_IMPORT_FAILED, 
                i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMN_EXPLANATION));
          } else {
          
            if (bpmnModel.getLocationMap().isEmpty()) {
              notificationManager.showErrorNotification(Messages.MODEL_IMPORT_INVALID_BPMNDI,
                  i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMNDI_EXPLANATION));
            } else {
          
              BpmnJsonConverter converter = new BpmnJsonConverter();
              ObjectNode modelNode = converter.convertToJson(bpmnModel);
              Model modelData = repositoryService.newModel();
              
              ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
              modelObjectNode.put(MODEL_NAME, processDefinition.getName());
              modelObjectNode.put(MODEL_REVISION, 1);
              modelObjectNode.put(MODEL_DESCRIPTION, processDefinition.getDescription());
              modelData.setMetaInfo(modelObjectNode.toString());
              modelData.setName(processDefinition.getName());
              
              repositoryService.saveModel(modelData);
              
              repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
              
              close();
              ExplorerApp.get().getViewManager().showEditorProcessDefinitionPage(modelData.getId());

	          URL explorerURL = ExplorerApp.get().getURL();
	          URL url = new URL(explorerURL.getProtocol(), explorerURL.getHost(), explorerURL.getPort(),
			          explorerURL.getPath().replace("/ui", "") + "modeler.html?modelId=" + modelData.getId());
              ExplorerApp.get().getMainWindow().open(new ExternalResource(url));
            }
          }
          
        } catch(Exception e) {
          notificationManager.showErrorNotification("error", e);
        }

複製代碼

四、根據流程定義ID查詢流程定義信息

分爲兩種:

  • 經過 rest 接口獲取流程 diagram 信息,並經過 Diagram Viewer 模塊渲染圖標
  • 直接獲取圖片
方式一:經過 rest 接口獲取流程 diagram 信息
// ProcessDefinitionDiagramLayoutResource.java

@RestController
public class ProcessDefinitionDiagramLayoutResource extends BaseProcessDefinitionDiagramLayoutResource {

  @RequestMapping(value="/process-definition/{processDefinitionId}/diagram-layout", method = RequestMethod.GET, produces = "application/json")
  public ObjectNode getDiagram(@PathVariable String processDefinitionId) {
    return getDiagramNode(null, processDefinitionId);
  }
}
複製代碼

!注意獲取 processDefinition 和 deployment 的方法:

// AbstractProcessDefinitionDetailPanel.java
// Members
  protected ProcessDefinition processDefinition;
  protected Deployment deployment;

    this.processDefinition = repositoryService.getProcessDefinition(processDefinitionId);

    if(processDefinition != null) {
      deployment = repositoryService.createDeploymentQuery().deploymentId(processDefinition.getDeploymentId()).singleResult();
    }
複製代碼
方式二:直接獲取圖片
// ProcessDefinitionInfoComponent.java

StreamResource diagram = null;
      
      // Try generating process-image stream
      if(processDefinition.getDiagramResourceName() != null) {
         diagram = new ProcessDefinitionImageStreamResourceBuilder()
          .buildStreamResource(processDefinition, repositoryService);
      }
複製代碼

流程設計工做區:EditorProcessDefinitionPage

主要關注 8 個功能的代碼:

  • 查詢流程設計工做區模型列表
  • 根據模型Id獲取模型信息
  • 新建模型
  • 刪除模型
  • 部署模型
  • 導入模型
  • 導出模型
  • 查詢左側stencilset列表信息

一、查詢流程設計工做區模型列表

獲取列表方法:

List<Model> modelList = repositoryService.createModelQuery().list();
複製代碼

Model的結構以下:

image

二、根據模型Id獲取模型信息

// ModelEditorJsonRestResource.java


@RequestMapping(value="/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json")
  public ObjectNode getEditorJson(@PathVariable String modelId) {
    ObjectNode modelNode = null;
    
    Model model = repositoryService.getModel(modelId);
      
    if (model != null) {
      try {
        if (StringUtils.isNotEmpty(model.getMetaInfo())) {
          modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
        } else {
          modelNode = objectMapper.createObjectNode();
          modelNode.put(MODEL_NAME, model.getName());
        }
        modelNode.put(MODEL_ID, model.getId());
        ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
            new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
        modelNode.put("model", editorJsonNode);
        
      } catch (Exception e) {
        LOGGER.error("Error creating model JSON", e);
        throw new ActivitiException("Error creating model JSON", e);
      }
    }
    return modelNode;
  }
複製代碼

三、新建模型

// NewModelPopupWindow.java

    ObjectMapper objectMapper = new ObjectMapper();
    ObjectNode editorNode = objectMapper.createObjectNode();
    editorNode.put("id", "canvas");
    editorNode.put("resourceId", "canvas");
    ObjectNode stencilSetNode = objectMapper.createObjectNode();
    stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
    editorNode.put("stencilset", stencilSetNode);
    Model modelData = repositoryService.newModel();
    
    ObjectNode modelObjectNode = objectMapper.createObjectNode();
    modelObjectNode.put(MODEL_NAME, (String) nameTextField.getValue());
    modelObjectNode.put(MODEL_REVISION, 1);
    String description = null;
    if (StringUtils.isNotEmpty((String) descriptionTextArea.getValue())) {
      description = (String) descriptionTextArea.getValue();
    } else {
      description = "";
    }
    modelObjectNode.put(MODEL_DESCRIPTION, description);
    modelData.setMetaInfo(modelObjectNode.toString());
    modelData.setName((String) nameTextField.getValue());
    
    repositoryService.saveModel(modelData);
    repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
複製代碼

四、刪除模型

repositoryService.deleteModel(modelData.getId());
複製代碼

五、部署模型

// EditorProcessDefinitionDetailPanel.java
protected void deployModelerModel(final ObjectNode modelNode) {
    BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
    byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
    
    String processName = modelData.getName() + ".bpmn20.xml";
    Deployment deployment = repositoryService.createDeployment()
            .name(modelData.getName())
            .addString(processName, new String(bpmnBytes))
            .deploy();

    ExplorerApp.get().getViewManager().showDeploymentPage(deployment.getId());
  }
複製代碼

六、導入模型

參考這段代碼:

// ImportUploadReceiver.java
public class ImportUploadReceiver implements Receiver, FinishedListener, ModelDataJsonConstants {
  
  ...

  protected void deployUploadedFile() {
    try {
      try {
        if (fileName.endsWith(".bpmn20.xml") || fileName.endsWith(".bpmn")) {
          validFile = true;
            
          XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
          InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()), "UTF-8");
          XMLStreamReader xtr = xif.createXMLStreamReader(in);
          BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
          
          if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {
            notificationManager.showErrorNotification(Messages.MODEL_IMPORT_FAILED, 
                i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMN_EXPLANATION));
          } else {
          
            if (bpmnModel.getLocationMap().isEmpty()) {
              notificationManager.showErrorNotification(Messages.MODEL_IMPORT_INVALID_BPMNDI,
                  i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMNDI_EXPLANATION));
            } else {
            
              String processName = null;
              if (StringUtils.isNotEmpty(bpmnModel.getMainProcess().getName())) {
                processName = bpmnModel.getMainProcess().getName();
              } else {
                processName = bpmnModel.getMainProcess().getId();
              }
              
              modelData = repositoryService.newModel();
              ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
              modelObjectNode.put(MODEL_NAME, processName);
              modelObjectNode.put(MODEL_REVISION, 1);
              modelData.setMetaInfo(modelObjectNode.toString());
              modelData.setName(processName);
              
              repositoryService.saveModel(modelData);
              
              BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
              ObjectNode editorNode = jsonConverter.convertToJson(bpmnModel);
              
              repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
            }
          }
        } else {
          notificationManager.showErrorNotification(Messages.MODEL_IMPORT_INVALID_FILE,
          		i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_FILE_EXPLANATION));
        }
      } catch (Exception e) {
        String errorMsg = e.getMessage().replace(System.getProperty("line.separator"), "<br/>");
        notificationManager.showErrorNotification(Messages.MODEL_IMPORT_FAILED, errorMsg);
      }
    } finally {
      if (outputStream != null) {
        try {
          outputStream.close();
        } catch (IOException e) {
          notificationManager.showErrorNotification("Server-side error", e.getMessage());
        }
      }
    }
  }
  
  protected void showUploadedDeployment() {
    viewManager.showEditorProcessDefinitionPage(modelData.getId());
  }
  
}
複製代碼

七、導出模型

// EditorProcessDefinitionDetailPanel.java

protected void exportModel() {
    final FileResource stream = new FileResource(new File(""), ExplorerApp.get()) {
      
      private static final long serialVersionUID = 1L;

        @Override
        public DownloadStream getStream() {
          DownloadStream ds = null;
          try {
            
            byte[] bpmnBytes = null;
            String filename = null;
            if (SimpleTableEditorConstants.TABLE_EDITOR_CATEGORY.equals(modelData.getCategory())) {
              WorkflowDefinition workflowDefinition = ExplorerApp.get().getSimpleWorkflowJsonConverter()
              		.readWorkflowDefinition(repositoryService.getModelEditorSource(modelData.getId()));
              
              filename = workflowDefinition.getName();
              WorkflowDefinitionConversion conversion = 
                      ExplorerApp.get().getWorkflowDefinitionConversionFactory().createWorkflowDefinitionConversion(workflowDefinition);
              bpmnBytes = conversion.getBpmn20Xml().getBytes("utf-8");
            } else {
            	JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
              BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
              BpmnModel model = jsonConverter.convertToBpmnModel(editorNode);
              filename = model.getMainProcess().getId() + ".bpmn20.xml";
              bpmnBytes = new BpmnXMLConverter().convertToXML(model);
            }
           
            ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
            ds = new DownloadStream(in, "application/xml", filename);
            // Need a file download POPUP
            ds.setParameter("Content-Disposition", "attachment; filename=" + filename);
          } catch(Exception e) {
            LOGGER.error("failed to export model to BPMN XML", e);
            ExplorerApp.get().getNotificationManager().showErrorNotification(Messages.PROCESS_TOXML_FAILED, e);
          }
          return ds;
        }
    };
    stream.setCacheTime(0);
    ExplorerApp.get().getMainWindow().open(stream);
  }
複製代碼

八、查詢左側stencilset列表信息

// StencilsetRestResource.java

public class StencilsetRestResource {
  
  @RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
  public @ResponseBody String getStencilset() {
    InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json");
    try {
      return IOUtils.toString(stencilsetStream, "utf-8");
    } catch (Exception e) {
      throw new ActivitiException("Error while loading stencil set", e);
    }
  }
}
複製代碼
相關文章
相關標籤/搜索