完善工作流 model 的接口

This commit is contained in:
YunaiV 2022-01-01 17:16:01 +08:00
parent dc7a434380
commit e47d5afcfa
11 changed files with 123 additions and 40 deletions

View File

@ -19,7 +19,7 @@ import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "流程定义") @Api(tags = "流程模型")
@RestController @RestController
@RequestMapping("/bpm/model") @RequestMapping("/bpm/model")
@Validated @Validated

View File

@ -7,7 +7,7 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
/** /**
* 流程定义 Base VO提供给添加修改详细的子 VO 使用 * 流程模型 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成 * 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/ */
@Data @Data

View File

@ -8,7 +8,7 @@ import lombok.ToString;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
@ApiModel("流程定义的创建 Request VO") @ApiModel("流程模型的创建 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
@ -18,6 +18,4 @@ public class BpmModelCreateReqVO extends BpmModelBaseVO {
@NotEmpty(message = "BPMN XML 不能为空") @NotEmpty(message = "BPMN XML 不能为空")
private String bpmnXml; private String bpmnXml;
// @ApiModelProperty(value = "版本号")
// private Integer revision;
} }

View File

@ -8,7 +8,7 @@ import lombok.ToString;
import java.util.Date; import java.util.Date;
@ApiModel("流程定义 Response VO") @ApiModel("流程模型 Response VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
@ -17,13 +17,27 @@ public class BpmModelRespVO extends BpmModelBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1024") @ApiModelProperty(value = "编号", required = true, example = "1024")
private String id; private String id;
@ApiModelProperty(value = "版本", required = true, example = "1")
private Integer revision;
@ApiModelProperty(value = "表单名字", example = "请假表单") @ApiModelProperty(value = "表单名字", example = "请假表单")
private String formName; private String formName;
@ApiModelProperty(value = "创建时间", required = true) @ApiModelProperty(value = "创建时间", required = true)
private Date createTime; private Date createTime;
/**
* 最新部署的流程定义
*/
private ProcessDefinition processDefinition;
@ApiModel("流程定义")
@Data
public static class ProcessDefinition {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private String id;
@ApiModelProperty(value = "版本", required = true, example = "1")
private Integer version;
}
} }

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
@ApiModel("流程定义分页 Request VO") @ApiModel("流程模型分页 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionPageReqVo; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionPageReqVo;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionRespVO; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionRespVO;
import cn.iocoder.yudao.adminserver.modules.bpm.service.workflow.BpmProcessDefinitionService; import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmDefinitionService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
@ -30,7 +30,7 @@ public class ProcessDefinitionController {
@Resource @Resource
private ProcessRuntime processRuntime; private ProcessRuntime processRuntime;
@Resource @Resource
private BpmProcessDefinitionService bpmProcessDefinitionService; private BpmDefinitionService bpmProcessDefinitionService;
@GetMapping(value = "/getStartForm") @GetMapping(value = "/getStartForm")

View File

@ -6,8 +6,8 @@ import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.form.BpmFormDO;
import cn.iocoder.yudao.adminserver.modules.bpm.service.model.dto.BpmModelMetaInfoRespDTO; import cn.iocoder.yudao.adminserver.modules.bpm.service.model.dto.BpmModelMetaInfoRespDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import org.activiti.engine.impl.persistence.entity.ModelEntity;
import org.activiti.engine.repository.Model; import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -25,15 +25,17 @@ public interface ModelConvert {
ModelConvert INSTANCE = Mappers.getMapper(ModelConvert.class); ModelConvert INSTANCE = Mappers.getMapper(ModelConvert.class);
default List<BpmModelRespVO> convertList(List<Model> list, Map<Long, BpmFormDO> formMap) { default List<BpmModelRespVO> convertList(List<Model> list, Map<Long, BpmFormDO> formMap,
Map<String, ProcessDefinition> processDefinitionMap) {
return CollectionUtils.convertList(list, model -> { return CollectionUtils.convertList(list, model -> {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
return convert(model, form); ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
return convert(model, form, processDefinition);
}); });
} }
default BpmModelRespVO convert(Model model, BpmFormDO form) { default BpmModelRespVO convert(Model model, BpmFormDO form, ProcessDefinition processDefinition) {
BpmModelRespVO modelRespVO = new BpmModelRespVO(); BpmModelRespVO modelRespVO = new BpmModelRespVO();
modelRespVO.setId(model.getId()); modelRespVO.setId(model.getId());
modelRespVO.setName(model.getName()); modelRespVO.setName(model.getName());
@ -48,10 +50,7 @@ public interface ModelConvert {
modelRespVO.setFormId(form.getId()); modelRespVO.setFormId(form.getId());
modelRespVO.setFormName(form.getName()); modelRespVO.setFormName(form.getName());
} }
if (model instanceof ModelEntity) { modelRespVO.setProcessDefinition(this.convert(processDefinition));
ModelEntity modelEntity = (ModelEntity) model;
modelRespVO.setRevision(modelEntity.getRevision());
}
return modelRespVO; return modelRespVO;
} }
@ -69,4 +68,6 @@ public interface ModelConvert {
return metaInfo; return metaInfo;
} }
BpmModelRespVO.ProcessDefinition convert(ProcessDefinition bean);
} }

View File

@ -1,15 +1,21 @@
package cn.iocoder.yudao.adminserver.modules.bpm.service.workflow; package cn.iocoder.yudao.adminserver.modules.bpm.service.definition;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionPageReqVo; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionPageReqVo;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionRespVO; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionRespVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.activiti.engine.repository.ProcessDefinition;
import java.util.List;
import java.util.Set;
/** /**
* 流程定义接口 * 流程定义接口
*
* @author yunlong.li * @author yunlong.li
*/ */
public interface BpmProcessDefinitionService { public interface BpmDefinitionService {
/** /**
* 流程定义分页 * 流程定义分页
* @param processDefinitionPageReqVo 分页入参 * @param processDefinitionPageReqVo 分页入参
@ -23,4 +29,13 @@ public interface BpmProcessDefinitionService {
* @return 分页model * @return 分页model
*/ */
FileResp export(String processDefinitionId); FileResp export(String processDefinitionId);
/**
* 获得 deploymentId 对应的 ProcessDefinition 数组
*
* @param deploymentId 部署编号
* @return 流程定义的数组
*/
List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentId);
} }

View File

@ -1,35 +1,34 @@
package cn.iocoder.yudao.adminserver.modules.bpm.service.model; package cn.iocoder.yudao.adminserver.modules.bpm.service.model;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.BpmModelRespVO;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.ModelPageReqVO;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.BpmModelCreateReqVO; import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.BpmModelCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.BpmModelRespVO;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.ModelPageReqVO;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.activiti.engine.repository.Model;
/** /**
* 流程定义接口 * 流程模型接口
* *
* @author yunlongn * @author yunlongn
*/ */
public interface BpmModelService { public interface BpmModelService {
/** /**
* 获得流程定义分页 * 获得流程模型分页
* *
* @param pageVO 分页查询 * @param pageVO 分页查询
* @return 流程定义分页 * @return 流程模型分页
*/ */
PageResult<BpmModelRespVO> getModelPage(ModelPageReqVO pageVO); PageResult<BpmModelRespVO> getModelPage(ModelPageReqVO pageVO);
// TODO @Li不用返回 CommonResult // TODO @Li不用返回 CommonResult
// TODO @LicreateBpmModal // TODO @LicreateBpmModal
/** /**
* 创建流程定义 * 创建流程模型
* *
* @param modelVO 创建信息 * @param modelVO 创建信息
* @return 创建的流程定义的编号 * @return 创建的流程模型的编号
*/ */
String createModel(BpmModelCreateReqVO modelVO); String createModel(BpmModelCreateReqVO modelVO);

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.adminserver.modules.bpm.service.workflow.impl; package cn.iocoder.yudao.adminserver.modules.bpm.service.model.impl;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.BpmModelCreateReqVO; import cn.iocoder.yudao.adminserver.modules.bpm.controller.model.vo.BpmModelCreateReqVO;
@ -8,6 +8,7 @@ import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp;
import cn.iocoder.yudao.adminserver.modules.bpm.convert.model.ModelConvert; import cn.iocoder.yudao.adminserver.modules.bpm.convert.model.ModelConvert;
import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.form.BpmFormDO; import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.form.BpmFormDO;
import cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants; import cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants;
import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmDefinitionService;
import cn.iocoder.yudao.adminserver.modules.bpm.service.form.BpmFormService; import cn.iocoder.yudao.adminserver.modules.bpm.service.form.BpmFormService;
import cn.iocoder.yudao.adminserver.modules.bpm.service.model.BpmModelService; import cn.iocoder.yudao.adminserver.modules.bpm.service.model.BpmModelService;
import cn.iocoder.yudao.adminserver.modules.bpm.service.model.dto.BpmModelMetaInfoRespDTO; import cn.iocoder.yudao.adminserver.modules.bpm.service.model.dto.BpmModelMetaInfoRespDTO;
@ -23,6 +24,7 @@ import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model; import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery; import org.activiti.engine.repository.ModelQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -35,16 +37,15 @@ import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.BPM_MODEL_KEY_EXISTS; import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.BPM_MODEL_KEY_EXISTS;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/** /**
* 流程定义实现 * 流程定义实现
* 主要进行 Activiti {@link Model} 的维护
* *
* @author yunlongn * @author yunlongn
*/ */
@ -53,10 +54,14 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
@Slf4j @Slf4j
public class BpmModelServiceImpl implements BpmModelService { public class BpmModelServiceImpl implements BpmModelService {
private static final String BPMN_FILE_SUFFIX = ".bpmn";
@Resource @Resource
private RepositoryService repositoryService; private RepositoryService repositoryService;
@Resource @Resource
private BpmFormService bpmFormService; private BpmFormService bpmFormService;
@Resource
private BpmDefinitionService bpmDefinitionService;
@Override @Override
public PageResult<BpmModelRespVO> getModelPage(ModelPageReqVO pageVO) { public PageResult<BpmModelRespVO> getModelPage(ModelPageReqVO pageVO) {
@ -67,7 +72,6 @@ public class BpmModelServiceImpl implements BpmModelService {
// 执行查询 // 执行查询
List<Model> models = modelQuery.orderByCreateTime().desc() List<Model> models = modelQuery.orderByCreateTime().desc()
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
long modelCount = modelQuery.count();
// 获得 Form Map // 获得 Form Map
Set<Long> formIds = CollectionUtils.convertSet(models, model -> { Set<Long> formIds = CollectionUtils.convertSet(models, model -> {
@ -75,8 +79,16 @@ public class BpmModelServiceImpl implements BpmModelService {
return metaInfo != null ? metaInfo.getFormId() : null; return metaInfo != null ? metaInfo.getFormId() : null;
}); });
Map<Long, BpmFormDO> formMap = bpmFormService.getFormMap(formIds); Map<Long, BpmFormDO> formMap = bpmFormService.getFormMap(formIds);
// 获得 ProcessDefinition Map
Set<String> deploymentIds = new HashSet<>();
models.forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getId()));
List<ProcessDefinition> processDefinitions = bpmDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
// 拼接结果 // 拼接结果
return new PageResult<>(ModelConvert.INSTANCE.convertList(models, formMap), modelCount); long modelCount = modelQuery.count();
return new PageResult<>(ModelConvert.INSTANCE.convertList(models, formMap, processDefinitionMap), modelCount);
} }
@Override @Override
@ -98,6 +110,38 @@ public class BpmModelServiceImpl implements BpmModelService {
return model.getId(); return model.getId();
} }
// @Override
// @Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作所以开启事务
// public String createModel(BpmModelCreateReqVO createReqVO) {
// Deployment deploy = repositoryService.createDeployment()
// .key(createReqVO.getKey()).name(createReqVO.getName()).category(createReqVO.getCategory())
// .addString(createReqVO.getName() + BPMN_FILE_SUFFIX, createReqVO.getBpmnXml())
// .deploy();
// // 设置 ProcessDefinition category 分类
// ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
// repositoryService.setProcessDefinitionCategory(definition.getId(), createReqVO.getCategory());
// return definition.getId();
// }
// @Override
// @Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作所以开启事务
// public String createModel(BpmModelCreateReqVO createReqVO) {
// // 校验流程标识已经存在
// Model keyModel = this.getModelByKey(createReqVO.getKey());
// if (keyModel != null) {
// throw exception(BPM_MODEL_KEY_EXISTS);
// }
//
// // 创建流程定义
// Model model = repositoryService.newModel();
// ModelConvert.INSTANCE.copy(model, createReqVO);
// // 保存流程定义
// repositoryService.saveModel(model);
// // 添加 BPMN XML
// repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(createReqVO.getBpmnXml()));
// return model.getId();
// }
@Override @Override
public CommonResult<String> updateModel(BpmModelCreateReqVO modelVO) { public CommonResult<String> updateModel(BpmModelCreateReqVO modelVO) {
// try { // try {

View File

@ -1,11 +1,12 @@
package cn.iocoder.yudao.adminserver.modules.bpm.service.workflow.impl; package cn.iocoder.yudao.adminserver.modules.bpm.service.workflow.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.FileResp;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionPageReqVo; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionPageReqVo;
import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionRespVO; import cn.iocoder.yudao.adminserver.modules.bpm.controller.workflow.vo.processdefinition.ProcessDefinitionRespVO;
import cn.iocoder.yudao.adminserver.modules.bpm.convert.workflow.ProcessDefinitionConvert; import cn.iocoder.yudao.adminserver.modules.bpm.convert.workflow.ProcessDefinitionConvert;
import cn.iocoder.yudao.adminserver.modules.bpm.service.workflow.BpmProcessDefinitionService; import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmDefinitionService;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -17,7 +18,9 @@ import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery; import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -27,7 +30,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService { public class BpmProcessDefinitionServiceImpl implements BpmDefinitionService {
private final RepositoryService repositoryService; private final RepositoryService repositoryService;
@ -57,4 +60,13 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
fileResp.setFileByte(bpmnBytes); fileResp.setFileByte(bpmnBytes);
return fileResp; return fileResp;
} }
@Override
public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
if (CollUtil.isEmpty(deploymentIds)) {
return Collections.emptyList();
}
return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
}
} }