Commit 3f1bc45c authored by 雍欢's avatar 雍欢

CommonDomainService中使用原生SQL的地方尽量替换成JPA的方式。

parent 09990a2a
package com.huigou.data.domain.service;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import com.huigou.context.MessageSourceContext;
import com.huigou.data.domain.EntityUtil;
import com.huigou.data.domain.model.AbstractEntity;
import com.huigou.data.domain.model.BaseInfoAbstractEntity;
import com.huigou.data.domain.model.BaseInfoStatus;
import com.huigou.data.domain.model.BaseInfoWithFolderAbstractEntity;
import com.huigou.data.domain.model.CommonDomainConstants;
import com.huigou.data.domain.model.FlowBillAbstractEntity;
import com.huigou.data.domain.model.MessageConstants;
import com.huigou.data.domain.model.TreeEntity;
import com.huigou.data.domain.model.*;
import com.huigou.data.domain.query.CheckBaseInfoDuplicateParameter;
import com.huigou.data.domain.query.QueryParameter;
import com.huigou.data.query.executor.SQLExecutorDao;
import com.huigou.data.query.model.QueryDescriptor;
import com.huigou.data.repository.GeneralRepositorySuper;
import com.huigou.domain.IdentifiedEntity;
import com.huigou.exception.ApplicationException;
import com.huigou.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.persistence.Column;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
/**
* 通用领域服务
......@@ -38,6 +34,8 @@ import com.huigou.util.StringUtil;
* @author gongmm
*/
public class CommonDomainService {
private final static Logger LOG = LoggerFactory.getLogger(CommonDomainService.class);
private static final String COMMON_XML_FILE_PATH = "config/uasp/query/bmp/common.xml";
protected GeneralRepositorySuper generalRepository;
......@@ -100,78 +98,165 @@ public class CommonDomainService {
/**
* 移动
*
* @param clazz 实体类型
* @param parentIdFieldName 父ID字段名称
* @param parentId 父ID
* @param ids ID列表
* @param clazz 实体类型
* @param parentIdColumnName 父ID对应的数据库表列名
* @param parentId 父ID
* @param ids ID列表
*/
@Transactional(rollbackFor = RuntimeException.class)
public void move(Class<? extends AbstractEntity> clazz, List<String> ids, String parentIdFieldName, String parentId) {
public void move(Class<? extends AbstractEntity> clazz, List<String> ids, String parentIdColumnName, String parentId) {
Assert.notNull(clazz, MessageSourceContext.getMessage(MessageConstants.CLAZZ_NOT_NULL));
Assert.notEmpty(ids, MessageSourceContext.getMessage(MessageConstants.ID_NOT_BLANK));
Assert.hasText(parentIdFieldName, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_FIELD_NAME_NOT_BLANK));
Assert.hasText(parentIdColumnName, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_FIELD_NAME_NOT_BLANK));
Assert.hasText(parentId, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_NOT_BLANK));
// update %s set %s = :parentId, version = (select next_sequence('version_seq')) where id in :ids
String jpql = new StringBuilder("update ")
.append(clazz.getName())
.append(" set ")
.append(parentIdFieldName)
.append("=:parentId,version=:version where id=:id")
AbstractEntity moveTo = generalRepository.findOne(clazz, parentId);
ids.stream().map(id -> generalRepository.findOne(clazz, id))
.filter(Objects::nonNull)
.forEach(entity -> moveInternal(entity, parentIdColumnName, moveTo));
}
/**
* 移动树形节点。
*
* @param node 待移动的节点
* @param parentIdColumnName 父id对应的数据库列名
* @param moveTo 父。
*/
private void moveInternal(AbstractEntity node, String parentIdColumnName, AbstractEntity moveTo) {
// 实体类的属性信息
List<Attribute> attributes = generalRepository.getEntityManager().getMetamodel()
.getManagedTypes()
.stream()
.filter(managedType -> managedType.getJavaType() == node.getClass())
.map(ManagedType::getAttributes)
.flatMap(Collection::stream)
.collect(Collectors.toList());
Field parentIdField = attributes.stream()
.filter(attribute -> {
Field field = (Field) attribute.getJavaMember();
Column column = field.getAnnotation(Column.class);
String columnName = column != null && StringUtils.isNotBlank(column.name()) ? column.name() : field.getName();
return columnName.equals(parentIdColumnName);
})
.map(Attribute::getJavaMember)
.map(Field.class::cast)
.findAny()
.orElseThrow(() -> new ApplicationException(String.format("根据列名%s未找到%s对应的属性", parentIdColumnName, node.getClass())));
// 移动自己到...
parentIdField.setAccessible(true);
try {
parentIdField.set(node, moveTo.getId());
} catch (IllegalAccessException e) {
throw new ApplicationException(String.format("设置%s出错", parentIdField.getName()), e);
}
// 设置fullId
attributes.stream().filter(attribute -> attribute.getName().equals("fullId"))
.map(Attribute::getJavaMember)
.map(Field.class::cast)
.findAny()
.ifPresent(fullIdField -> {
try {
fullIdField.setAccessible(true);
String fullId = StringUtils.trimToEmpty((String) fullIdField.get(moveTo)) + "/" + node.getId();
fullIdField.set(node, fullId);
} catch (IllegalAccessException e) {
LOG.error("设置fullId出错", e);
}
});
// 设置fullName
attributes.stream().filter(attribute -> attribute.getName().equals("fullName"))
.map(Attribute::getJavaMember)
.map(Field.class::cast)
.findAny()
.ifPresent(fullNameField -> {
fullNameField.setAccessible(true);
attributes.stream().filter(attr -> attr.getName().equals("name"))
.map(Attribute::getJavaMember)
.map(Field.class::cast)
.findAny()
.ifPresent(nameField -> {
nameField.setAccessible(true);
try {
String name = (String) nameField.get(node);
String fullName = StringUtils.trimToEmpty((String) fullNameField.get(moveTo)) + "/" + name;
fullNameField.set(node, fullName);
} catch (IllegalAccessException e) {
LOG.error("设置fullName出错", e);
}
});
});
generalRepository.save(node);
// 递归移动子节点
String jpql = new StringBuilder("select t from ")
.append(node.getClass().getName())
.append(" t where ")
.append(parentIdField.getName())
.append("=:parentId")
.toString();
ids.forEach(id -> generalRepository.getEntityManager()
.createQuery(jpql)
.setParameter("parentId", parentId)
.setParameter("version", generalRepository.getVersionNextId())
.setParameter("id", id)
.executeUpdate());
generalRepository.getEntityManager().createQuery(jpql)
.setParameter("parentId", node.getId())
.getResultList()
.forEach(chlid -> moveInternal((TreeEntity) chlid, parentIdColumnName, node));
}
/**
* 移动树
*
* @param clazz 实体类型
* @param parentIdFieldName 父ID字段名称
* @param parentId 父ID
* @param ids ID列表
* @param clazz 实体类型
* @param parentIdColumnName 父ID对应的数据库列名
* @param parentId 父ID
* @param ids ID列表
*/
@SuppressWarnings("unchecked")
public void moveForTree(Class<? extends TreeEntity> clazz, List<String> ids, String parentIdFieldName, String parentId) {
public void moveForTree(Class<? extends TreeEntity> clazz, List<String> ids, String
parentIdColumnName, String parentId) {
Assert.notNull(clazz, MessageSourceContext.getMessage(MessageConstants.CLAZZ_NOT_NULL));
Assert.notEmpty(ids, MessageSourceContext.getMessage(MessageConstants.ID_NOT_BLANK));
Assert.hasText(parentIdFieldName, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_FIELD_NAME_NOT_BLANK));
Assert.hasText(parentIdColumnName, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_FIELD_NAME_NOT_BLANK));
Assert.hasText(parentId, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_NOT_BLANK));
String tableName = EntityUtil.getTableName(clazz);
List<TreeEntity> treeEntities = (List<TreeEntity>) generalRepository.findAll(clazz, ids);
TreeEntity moveToParent = generalRepository.findOne(clazz, parentId);
String parentFullId = moveToParent.getFullId();
// List<TreeEntity> treeEntities = (List<TreeEntity>) generalRepository.findAll(clazz, ids);
// TreeEntity moveToParent = generalRepository.findOne(clazz, parentId);
// String parentFullId = moveToParent.getFullId();
// 1、更新fullId 和fullName
String chlidFullId = null;
TreeEntity oldParent;
String sql = getSqlByName("updateFullIdAndName");
for (TreeEntity treeEntity : treeEntities) {
chlidFullId = treeEntity.getFullId();
// 校验不能循环引用
Assert.isTrue(parentFullId.indexOf(chlidFullId) == -1,
MessageSourceContext.getMessage(MessageConstants.UNABLE_TO_MOVE_TO, treeEntity.getName(), moveToParent.getName()));
oldParent = generalRepository.findOne(clazz, treeEntity.getParentId());
Map<String, Object> parameterMap = new HashMap<String, Object>(2);
parameterMap.put("parentNewFullId", moveToParent.getFullId());
parameterMap.put("parentOldFullId", oldParent.getFullId());
parameterMap.put("parentNewFullName", moveToParent.getFullName());
parameterMap.put("parentOldFullName", oldParent.getFullName());
parameterMap.put("likeFullId", chlidFullId + "%");
this.generalRepository.updateByNativeSql(String.format(sql, tableName), parameterMap);
}
// for (TreeEntity chlid : treeEntities) {
// // 校验不能循环引用
// if (parentFullId != null) {
// Assert.isTrue(parentFullId.indexOf(chlid.getFullId()) == -1, MessageSourceContext.getMessage(MessageConstants.UNABLE_TO_MOVE_TO, chlid.getName(), moveToParent.getName()));
// }
// TreeEntity oldParent = generalRepository.findOne(clazz, chlid.getParentId());
// updateFullIdAndName(clazz, moveToParent, oldParent, chlid);
// }
// 2、移动
sql = this.getSqlByName("moveSqlByParentId");
sql = String.format(sql, tableName, parentIdFieldName);
Map<String, Object> map = new HashMap<String, Object>(2);
map.put(CommonDomainConstants.PARENT_ID_FIELD_NAME, parentId);
map.put(CommonDomainConstants.IDS_FIELD_NAME, ids);
this.generalRepository.updateByNativeSql(sql, map);
AbstractEntity moveTo = generalRepository.findOne(clazz, parentId);
ids.stream().map(id -> generalRepository.findOne(clazz, id))
.filter(Objects::nonNull)
.forEach(entity -> moveInternal(entity, parentIdColumnName, moveTo));
}
private void updateFullIdAndName(Class<? extends TreeEntity> clazz, TreeEntity moveToParent, TreeEntity
oldParent, TreeEntity chlid) {
// update %s
// set full_id = concat(:parentNewFullId,substr(full_Id,length(:parentOldFullId) + 1,length(full_Id))),
// full_Name = concat(:parentNewFullName,substr(full_Name, length(:parentOldFullName) + 1,length(full_Name))),
// version = (select next_sequence('version_seq'))
// where full_Id like :likeFullId
String jpql = new StringBuilder("update ").append(clazz.getName())
.append(" set fullId=concat(:parentNewFullId,substring(fullId,length(:parentOldFullId) + 1,length(fullId))),")
.append("fullName=concat(:parentNewFullName,substring(fullName, length(:parentOldFullName) + 1,length(fullName))),")
.append("version=:version")
.append(" where fullId like :likeFullId")
.toString();
generalRepository.getEntityManager().createQuery(jpql)
.setParameter("parentNewFullId", moveToParent.getFullId())
.setParameter("parentOldFullId", oldParent.getFullId())
.setParameter("parentNewFullName", moveToParent.getFullName())
.setParameter("parentOldFullName", oldParent.getFullName())
.setParameter("version", generalRepository.getVersionNextId())
.setParameter("likeFullId", chlid.getFullId() + "%")
.executeUpdate();
}
/**
......@@ -187,36 +272,21 @@ public class CommonDomainService {
Assert.notEmpty(ids, MessageSourceContext.getMessage(MessageConstants.ID_NOT_BLANK));
Assert.hasText(parentId, MessageSourceContext.getMessage(MessageConstants.PARENT_ID_NOT_BLANK));
String tableName = EntityUtil.getTableName(clazz);
TreeEntity moveToParent = generalRepository.findOne(clazz, parentId);
Assert.notNull(moveToParent, MessageSourceContext.getMessage(MessageConstants.OBJECT_NOT_NULL));
String parentFullId = moveToParent.getFullId();
List<TreeEntity> treeEntities = (List<TreeEntity>) generalRepository.findAll(clazz, ids);
String chlidFullId = null;
TreeEntity oldParent;
String sql = getSqlByName("updateFullIdAndName");
for (TreeEntity treeEntity : treeEntities) {
chlidFullId = treeEntity.getFullId();
// 校验不能循环引用
Assert.isTrue(parentFullId.indexOf(chlidFullId) == -1,
MessageSourceContext.getMessage(MessageConstants.UNABLE_TO_MOVE_TO, treeEntity.getName(), moveToParent.getName()));
oldParent = generalRepository.findOne(clazz, treeEntity.getParentId());
Assert.notNull(oldParent, MessageSourceContext.getMessage(MessageConstants.UNABLE_TO_MOVE_TO, treeEntity.getName(), moveToParent.getName()));
Map<String, Object> param = new HashMap<String, Object>();
param.put("parentNewFullId", parentFullId);
param.put("parentOldFullId", oldParent.getFullId());
param.put("parentNewFullName", moveToParent.getFullName());
param.put("parentOldFullName", oldParent.getFullName());
param.put("likeFullId", chlidFullId + "%");
this.generalRepository.updateByNativeSql(String.format(sql, tableName), param);
}
sql = this.getSqlByName("moveSqlByParentId");
sql = String.format(sql, tableName, CommonDomainConstants.PARENT_ID_COLUMN_NAME);
Map<String, Object> parameterMap = new HashMap<String, Object>(2);
parameterMap.put(CommonDomainConstants.PARENT_ID_FIELD_NAME, parentId);
parameterMap.put(CommonDomainConstants.IDS_FIELD_NAME, ids);
this.generalRepository.updateByNativeSql(sql, parameterMap);
// for (TreeEntity child : treeEntities) {
// // 校验不能循环引用
// Assert.isTrue(parentFullId.indexOf(child.getFullId()) == -1, MessageSourceContext.getMessage(MessageConstants.UNABLE_TO_MOVE_TO, child.getName(), moveToParent.getName()));
// TreeEntity oldParent = generalRepository.findOne(clazz, child.getParentId());
// Assert.notNull(oldParent, MessageSourceContext.getMessage(MessageConstants.UNABLE_TO_MOVE_TO, child.getName(), moveToParent.getName()));
// updateFullIdAndName(clazz, moveToParent, oldParent, child);
// }
TreeEntity moveTo = generalRepository.findOne(clazz, parentId);
ids.stream().map(id -> generalRepository.findOne(clazz, id))
.filter(Objects::nonNull)
.forEach(id -> moveInternal(id, CommonDomainConstants.PARENT_ID_COLUMN_NAME, moveTo));
}
/**
......@@ -231,20 +301,23 @@ public class CommonDomainService {
Assert.notNull(clazz, MessageSourceContext.getMessage(MessageConstants.CLAZZ_NOT_NULL));
Assert.hasText(folderId, MessageSourceContext.getMessage(MessageConstants.FOLDER_ID_NOT_BLANK));
Assert.notEmpty(ids, MessageSourceContext.getMessage(MessageConstants.ID_NOT_BLANK));
String tableName = EntityUtil.getTableName(clazz);
String sql = this.getSqlByName("moveSqlByFolderId");
sql = String.format(sql, tableName);
Map<String, Object> parameterMap = new HashMap<String, Object>(2);
parameterMap.put(CommonDomainConstants.FOLDER_ID_FIELD_NAME, folderId);
parameterMap.put(CommonDomainConstants.IDS_FIELD_NAME, ids);
this.generalRepository.updateByNativeSql(sql, parameterMap);
// update %s set folder_Id = :folderId, version = (select next_sequence('version_seq')) where id in :ids
String jpql = new StringBuilder("update ")
.append(clazz.getName())
.append(" set ")
.append(CommonDomainConstants.FOLDER_ID_FIELD_NAME)
.append("=:folderId,version=:version where id=:id")
.toString();
ids.forEach(id -> generalRepository.getEntityManager().createQuery(jpql)
.setParameter("folderId", folderId)
.setParameter("version", generalRepository.getVersionNextId())
.setParameter("id", id)
.executeUpdate());
}
@SuppressWarnings("unchecked")
public List<? extends IdentifiedEntity> findDuplicateEntities(Class<? extends IdentifiedEntity> clazz, CheckBaseInfoDuplicateParameter parameter) {
public List<? extends IdentifiedEntity> findDuplicateEntities(Class<? extends
IdentifiedEntity> clazz, CheckBaseInfoDuplicateParameter parameter) {
Assert.notNull(clazz, MessageSourceContext.getMessage(MessageConstants.CLAZZ_NOT_NULL));
Assert.notNull(parameter, MessageSourceContext.getMessage(MessageConstants.PARAMETER_NOT_NULL_FORMAT, "parameter"));
parameter.checkConstraints();
......@@ -264,10 +337,12 @@ public class CommonDomainService {
Integer sequence = params.get(id);
String jpql = new StringBuilder("update ")
.append(clazz.getName())
.append(" set sequence=:sequence,version=:version where id=:id")
.append(" set ")
.append(CommonDomainConstants.SEQUENCE_FIELD_NAME)
.append("=:sequence,version=:version where id=:id")
.toString();
int affectedRows = generalRepository.getEntityManager().createQuery(jpql)
.setParameter(CommonDomainConstants.SEQUENCE_FIELD_NAME, sequence)
.setParameter("sequence", sequence)
.setParameter("version", generalRepository.getVersionNextId())
.setParameter("id", id)
.executeUpdate();
......@@ -305,21 +380,28 @@ public class CommonDomainService {
Assert.isTrue(affectedRows <= 1, String.format("根据主键修改状态出错,匹配到 %d 条数据", affectedRows));
}
public void updateChildenFullName(Class<? extends AbstractEntity> clazz, String fullId, String oldFullName, String newFullName) {
String tableName = EntityUtil.getTableName(clazz);
String jpql = getSqlByName("updateFullName");
jpql = String.format(jpql, tableName);
Map<String, Object> params = new HashMap<String, Object>(3);
params.put("fullId", fullId + "/%");
params.put("oldFullName", oldFullName);
params.put("newFullName", newFullName);
this.generalRepository.updateByNativeSql(jpql, params);
public void updateChildenFullName(Class<? extends AbstractEntity> clazz, String fullId, String
oldFullName, String newFullName) {
// update %s
// set full_Name = concat(:newFullName, substr(full_Name,length(:oldFullName) + 1,length(full_Name))),
// version = (select next_sequence('version_seq'))
// where full_id like :fullId
String jpql = new StringBuilder("update ")
.append(clazz.getName())
.append(" set fullName=concat(:newFullName, substring(full_Name,length(:oldFullName) + 1,length(full_Name))),version=:version")
.append(" where fullId like :fullId")
.toString();
generalRepository.getEntityManager().createQuery(jpql)
.setParameter("newFullName", newFullName)
.setParameter("oldFullName", oldFullName)
.setParameter("version", generalRepository.getVersionNextId())
.setParameter("fullId", fullId + "/%")
.executeUpdate();
}
@SuppressWarnings({"rawtypes", "unchecked"})
public BaseInfoWithFolderAbstractEntity saveBaseInfoWithFolderEntity(BaseInfoWithFolderAbstractEntity entity, JpaRepository repository) {
public BaseInfoWithFolderAbstractEntity saveBaseInfoWithFolderEntity(BaseInfoWithFolderAbstractEntity
entity, JpaRepository repository) {
Assert.notNull(entity, MessageSourceContext.getMessage(MessageConstants.OBJECT_NOT_NULL));
Assert.notNull(repository, MessageSourceContext.getMessage(MessageConstants.REPOSITORY_NOT_NULL));
......@@ -354,7 +436,7 @@ public class CommonDomainService {
if (entity.isNew()) {
String jpql = new StringBuilder("select count(e) from ")
.append(entity.getClass().getName())
.append(" e where billCode=:billCode")
.append(" e where billCode=:billCode")
.toString();
int count = (int) generalRepository.getEntityManager().createQuery(jpql)
.setParameter("billCode", entity.getBillCode())
......@@ -391,7 +473,8 @@ public class CommonDomainService {
// }
@SuppressWarnings({"rawtypes", "unchecked"})
public TreeEntity saveTreeEntity(TreeEntity entity, JpaRepository repository, boolean useDefaultCheck, boolean globalCode) {
public TreeEntity saveTreeEntity(TreeEntity entity, JpaRepository repository, boolean useDefaultCheck,
boolean globalCode) {
Assert.notNull(entity, MessageSourceContext.getMessage(MessageConstants.PARAMETER_NOT_NULL_FORMAT, "entity"));
Assert.notNull(repository, MessageSourceContext.getMessage(MessageConstants.REPOSITORY_NOT_NULL));
......@@ -449,12 +532,14 @@ public class CommonDomainService {
}
@SuppressWarnings("rawtypes")
public TreeEntity saveTreeEntity(TreeEntity entity, JpaRepository repository, String oldName, boolean useDefaultCheck) {
public TreeEntity saveTreeEntity(TreeEntity entity, JpaRepository repository, String oldName,
boolean useDefaultCheck) {
return saveTreeEntity(entity, repository, oldName, useDefaultCheck, true);
}
@SuppressWarnings("rawtypes")
public TreeEntity saveTreeEntity(TreeEntity entity, JpaRepository repository, String oldName, boolean useDefaultCheck, boolean globalCode) {
public TreeEntity saveTreeEntity(TreeEntity entity, JpaRepository repository, String oldName,
boolean useDefaultCheck, boolean globalCode) {
Assert.notNull(entity, MessageSourceContext.getMessage(MessageConstants.OBJECT_NOT_NULL));
Assert.notNull(repository, MessageSourceContext.getMessage(MessageConstants.REPOSITORY_NOT_NULL));
// Assert.hasText(oldName, "参数oldName不能为空。");
......@@ -515,7 +600,7 @@ public class CommonDomainService {
return entity;
}
AbstractEntity result = (AbstractEntity) this.generalRepository.findOne(entity.getClass(), entity.getId());
AbstractEntity result = this.generalRepository.findOne(entity.getClass(), entity.getId());
Assert.notNull(result,
MessageSourceContext.getMessage(MessageConstants.LOAD_OBJECT_IS_NULL)
+ MessageSourceContext.getMessage(MessageConstants.OBJECT_NOT_FOUND_BY_ID, entity.getId(), entity.getClass().getName()));
......@@ -529,7 +614,7 @@ public class CommonDomainService {
return (T) entity;
}
AbstractEntity result = (AbstractEntity) this.generalRepository.findOne(entity.getClass(), entity.getId());
AbstractEntity result = this.generalRepository.findOne(entity.getClass(), entity.getId());
Assert.notNull(result,
MessageSourceContext.getMessage(MessageConstants.LOAD_OBJECT_IS_NULL)
+ MessageSourceContext.getMessage(MessageConstants.OBJECT_NOT_FOUND_BY_ID, entity.getId(), entity.getClass().getName()));
......@@ -544,5 +629,4 @@ public class CommonDomainService {
public String getCountByParentIdSql() {
return getSqlByName("countByParentId");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment