package jnpf.elnService.formula;
|
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.elnEntity.field.ElnFieldVersionEntity;
|
import jnpf.elnEntity.formula.ElnFormulaDefEntity;
|
import jnpf.elnEntity.formula.ElnFormulaForms;
|
import jnpf.elnEntity.formula.ElnFormulaParameterSlotEntity;
|
import jnpf.elnEntity.formula.ElnFormulaVersionEntity;
|
import jnpf.elnEntity.formula.ElnFormulaVos;
|
import jnpf.elnEntity.formula.ElnTemplateFormulaBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateVersionEntity;
|
import jnpf.elnMapper.ElnTemplateMapper;
|
import jnpf.elnMapper.ElnTemplateFormulaBindingMapper;
|
import jnpf.elnService.formula.impl.ElnTemplateFormulaBindingServiceImpl;
|
import jnpf.elnService.formula.impl.FormulaTemplateVersionCopyParticipant;
|
import jnpf.elnService.template.TemplateAnnotationAuthorization;
|
import jnpf.exception.DataException;
|
import jnpf.util.JsonUtil;
|
import org.apache.ibatis.annotations.Insert;
|
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Update;
|
import org.junit.Test;
|
import org.springframework.dao.DataAccessResourceFailureException;
|
|
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Method;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.Callable;
|
import java.util.concurrent.CyclicBarrier;
|
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.Executors;
|
import java.util.concurrent.Future;
|
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
|
public class ElnTemplateFormulaBindingServiceImplTest {
|
|
@Test
|
public void reservePersistsExactResultLockSnapshotAndReturnsItInContext() {
|
Fixture f = new Fixture();
|
ElnFormulaForms.TemplateBinding form = f.request("formula-current", "snapshot-op",
|
map("amount", "input"), "result");
|
form.setJieguoSuoKuaizhao(Arrays.asList(f.control("result", ""),
|
f.control("result-ref", "contentLocked")));
|
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(), form);
|
ElnFormulaVos.TemplateBinding fromContext = f.service.context(f.authorization())
|
.getBindings().get(0);
|
|
assertEquals(2, reserved.getJieguoSuoKuaizhao().size());
|
assertEquals("result", reserved.getJieguoSuoKuaizhao().get(0).getBindingId());
|
assertEquals("contentLocked", fromContext.getJieguoSuoKuaizhao().get(1).getLock());
|
}
|
|
@Test
|
public void versionCopyContextExposesInheritedFormulaResultLockBindingsBeforeFormulaCopyExists() {
|
Fixture f = new Fixture();
|
f.store.template.setLaiyuanMubanId("source-template");
|
f.store.template.setShengbanCaozuoBianma("copy-with-formula");
|
ElnTemplateBindingEntity result = f.store.fieldBindings.get("result");
|
result.setBangdingZhuangtai("PENDING");
|
result.setLaiyuanBangdingId("source-result");
|
ElnTemplateBindingEntity reference = f.store.fieldBindings.get("result-ref");
|
reference.setBangdingZhuangtai("PENDING");
|
reference.setLaiyuanBangdingId("source-result-ref");
|
ElnTemplateFormulaBindingEntity sourceFormula = f.formulaBinding(
|
"source-formula", "source-template", "ACTIVE");
|
sourceFormula.setJieguoBangdingId("source-result");
|
f.store.bindings.put(sourceFormula.getId(), sourceFormula);
|
|
ElnFormulaVos.TemplateMappingContext context = f.service.context(f.authorization());
|
|
assertTrue(context.getBindings().isEmpty());
|
assertEquals(Arrays.asList("result", "result-ref"),
|
context.getInheritedFormulaLockBindingIds());
|
}
|
|
@Test
|
public void reserveRejectsLockSnapshotOutsideFormulaResultControls() {
|
Fixture f = new Fixture();
|
ElnFormulaForms.TemplateBinding form = f.request("formula-current", "snapshot-invalid",
|
map("amount", "input"), "result");
|
form.setJieguoSuoKuaizhao(Arrays.asList(f.control("result", ""),
|
f.control("input", "")));
|
|
expectFailure("FORMULA_RESULT_LOCK_MISMATCH", () ->
|
f.service.reserve(f.authorization(), form));
|
}
|
|
@Test
|
public void reserveRejectsOperationCodeLongerThan160Characters() {
|
Fixture f = new Fixture();
|
|
expectFailure("长度", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", operationOfLength(161), map("amount", "input"), "result")));
|
}
|
|
@Test
|
public void activateRejectsLongOperationCodeWithoutChangingState() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", "activate-length", map("amount", "input"), "result"));
|
ElnFormulaForms.ActivateFormulaBinding activate = new ElnFormulaForms.ActivateFormulaBinding();
|
activate.setOperationCode(operationOfLength(161));
|
activate.setExpectedVersion(1);
|
activate.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("长度", () -> f.service.activate(f.authorization(), reserved.getId(), activate));
|
assertEquals("PENDING", f.store.binding(reserved.getId()).getBangdingZhuangtai());
|
}
|
|
@Test
|
public void upgradeCheckRejectsLongTargetOperationCode() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding source = f.activeMapping("upgrade-length-source");
|
ElnFormulaForms.TemplateBinding target = f.request("formula-current", operationOfLength(161),
|
map("amount", "input"), "result");
|
|
expectFailure("长度", () -> f.service.checkUpgrade(
|
f.authorization(), source.getId(), target));
|
}
|
|
@Test
|
public void versionCopyConfirmRejectsLongUpgradeOperationCode() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("version-copy-length");
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode(operationOfLength(161));
|
|
expectFailure("长度", () -> f.service.confirmVersionCopy(f.authorization(), confirm));
|
}
|
|
@Test
|
public void formulaBindingInsertIgnoresOnlyExactOperationConflictWithAllColumns() {
|
Method insertMethod = null;
|
for (Method method : ElnTemplateFormulaBindingMapper.class.getDeclaredMethods()) {
|
if (method.getAnnotation(Insert.class) != null) {
|
insertMethod = method;
|
break;
|
}
|
}
|
assertTrue(insertMethod != null);
|
assertEquals(int.class, insertMethod.getReturnType());
|
String sql = String.join(" ", insertMethod.getAnnotation(Insert.class).value())
|
.toLowerCase().replaceAll("\\s+", " ").trim();
|
assertTrue(sql.contains("insert into eln_template_formula_binding (muban_id, "
|
+ "gongshi_dingyi_id, gongshi_banben_id, canshu_yingshe, "
|
+ "jieguo_bangding_id, bangding_zhuangtai, tuopu_xuhao, caozuo_bianma, "
|
+ "jieguo_suo_kuaizhao, "
|
+ "laiyuan_gongshi_bangding_id, daoqi_shijian, zhuangtai_yuanyin, f_id, "
|
+ "f_tenant_id, f_version, f_creator_user_id, f_creator_time, "
|
+ "f_last_modify_user_id, f_last_modify_time, f_delete_user_id, "
|
+ "f_delete_time, f_delete_mark) values ("));
|
assertTrue(sql.contains("on conflict (f_tenant_id, muban_id, caozuo_bianma) "
|
+ "do nothing"));
|
}
|
|
@Test
|
public void templateActivationLockUsesMyBatisPlusRowLockInsteadOfRawSelect() throws Exception {
|
Method method = ElnTemplateMapper.class.getMethod(
|
"lockTemplateForUpdate", String.class, String.class);
|
assertTrue(method.isDefault());
|
assertTrue(method.getAnnotation(Select.class) == null);
|
}
|
|
@Test
|
public void formulaStatusUpdateUsesJsonbTypeHandlers() throws Exception {
|
Method method = ElnTemplateFormulaBindingMapper.class.getMethod(
|
"updateStatus", ElnTemplateFormulaBindingEntity.class,
|
int.class, String.class, String.class);
|
Update update = method.getAnnotation(Update.class);
|
assertTrue(update != null);
|
String sql = String.join(" ", update.value()).toLowerCase()
|
.replaceAll("\\s+", " ").trim();
|
|
assertTrue(sql.contains("canshu_yingshe = #{entity.canshuyingshe,jdbctype=other,"
|
+ "typehandler=jnpf.elnentity.formula.elntemplateformulabindingentity$"
|
+ "parametermappingtypehandler}"));
|
assertTrue(sql.contains("f_tenant_id = #{entity.ftenantid}"));
|
assertTrue(sql.contains("f_version = #{expectedversion}"));
|
assertTrue(sql.contains("bangding_zhuangtai = #{entity.bangdingzhuangtai}"));
|
}
|
|
@Test
|
public void reservesIdempotentlyAndActivatesOnlyWithReadOnlyResultControls() {
|
Fixture f = new Fixture();
|
ElnFormulaForms.TemplateBinding request = f.request("formula-current", "op-1",
|
map("amount", "input"), "result");
|
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(), request);
|
ElnFormulaVos.TemplateBinding retried = f.service.reserve(f.authorization(), request);
|
|
assertEquals(reserved.getId(), retried.getId());
|
assertEquals("PENDING", reserved.getStatus());
|
assertTrue(f.store.binding(reserved.getId()).getDaoqiShijian().after(new Date(0)));
|
|
ElnFormulaForms.ActivateFormulaBinding activate = new ElnFormulaForms.ActivateFormulaBinding();
|
activate.setOperationCode("op-1");
|
activate.setExpectedVersion(1);
|
activate.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
f.service.activate(f.authorization(), reserved.getId(), activate);
|
f.service.activate(f.authorization(), reserved.getId(), activate);
|
|
assertEquals("ACTIVE", f.store.binding(reserved.getId()).getBangdingZhuangtai());
|
assertEquals(Integer.valueOf(0), f.store.binding(reserved.getId()).getTuopuXuhao());
|
assertTrue(f.hasAudit("ELN_TEMPLATE_FORMULA_RESERVE"));
|
assertTrue(f.hasAudit("ELN_TEMPLATE_FORMULA_ACTIVATE"));
|
assertEquals(1, f.auditCount("ELN_TEMPLATE_FORMULA_ACTIVATE"));
|
assertFalse(f.audits.get(0).getDataSnapshot().toLowerCase().contains("ticket"));
|
}
|
|
@Test
|
public void concurrentActivationsOnSameTemplateSerializeAndRejectCycle() throws Exception {
|
ConcurrentGraphStore store = new ConcurrentGraphStore();
|
Fixture f = new Fixture(store);
|
f.addField("result-a", "result-a-version", "DECIMAL", manualBinding());
|
f.addField("result-b", "result-b-version", "DECIMAL", manualBinding());
|
|
ElnTemplateFormulaBindingEntity first = f.formulaBinding(
|
"formula-a", "template-1", "PENDING");
|
first.setCanshuYingshe(map("amount", "result-b"));
|
first.setJieguoBangdingId("result-a");
|
first.setCaozuoBianma("activate-a");
|
ElnTemplateFormulaBindingEntity second = f.formulaBinding(
|
"formula-b", "template-1", "PENDING");
|
second.setCanshuYingshe(map("amount", "result-a"));
|
second.setJieguoBangdingId("result-b");
|
second.setCaozuoBianma("activate-b");
|
store.bindings.put(first.getId(), first);
|
store.bindings.put(second.getId(), second);
|
|
ExecutorService executor = Executors.newFixedThreadPool(2);
|
try {
|
Future<String> firstResult = executor.submit(activation(
|
f, store, first.getId(), "activate-a", "result-a"));
|
Future<String> secondResult = executor.submit(activation(
|
f, store, second.getId(), "activate-b", "result-b"));
|
List<String> results = Arrays.asList(
|
firstResult.get(5, TimeUnit.SECONDS),
|
secondResult.get(5, TimeUnit.SECONDS));
|
|
assertEquals(1, Collections.frequency(results, "SUCCESS"));
|
assertTrue(results.toString(), results.stream()
|
.anyMatch(value -> value.contains("循环")));
|
assertEquals(2, store.lockCalls.get());
|
assertEquals(1, store.activeCount());
|
} finally {
|
executor.shutdownNow();
|
}
|
}
|
|
@Test
|
public void concurrentUpgradesOnSameTemplateSerializeAndRejectCycle() throws Exception {
|
ConcurrentGraphStore store = new ConcurrentGraphStore();
|
Fixture f = new Fixture(store);
|
f.addField("result-a", "upgrade-result-a-version", "DECIMAL", manualBinding());
|
f.addField("result-b", "upgrade-result-b-version", "DECIMAL", manualBinding());
|
|
ElnTemplateFormulaBindingEntity first = f.formulaBinding(
|
"upgrade-a", "template-1", "ACTIVE");
|
first.setJieguoBangdingId("result-a");
|
first.setCaozuoBianma("original-a");
|
ElnTemplateFormulaBindingEntity second = f.formulaBinding(
|
"upgrade-b", "template-1", "ACTIVE");
|
second.setJieguoBangdingId("result-b");
|
second.setCaozuoBianma("original-b");
|
store.bindings.put(first.getId(), first);
|
store.bindings.put(second.getId(), second);
|
|
ExecutorService executor = Executors.newFixedThreadPool(2);
|
try {
|
Future<String> firstResult = executor.submit(upgrade(
|
f, store, first.getId(), "upgrade-op-a", "result-b", "result-a"));
|
Future<String> secondResult = executor.submit(upgrade(
|
f, store, second.getId(), "upgrade-op-b", "result-a", "result-b"));
|
List<String> results = Arrays.asList(
|
firstResult.get(5, TimeUnit.SECONDS),
|
secondResult.get(5, TimeUnit.SECONDS));
|
|
assertEquals(1, Collections.frequency(results, "SUCCESS"));
|
assertTrue(results.toString(), results.stream()
|
.anyMatch(value -> value.contains("循环")));
|
assertEquals(2, store.lockCalls.get());
|
assertTrue(new FormulaDependencyGraph().validate(store.committedBindings()).isValid());
|
} finally {
|
executor.shutdownNow();
|
}
|
}
|
|
@Test
|
public void versionCopyConfirmationKeepsStateAndGraphAccessInsideTemplateLock() {
|
LockCheckingStore store = new LockCheckingStore();
|
Fixture f = new Fixture(store);
|
store.template.setShengbanCaozuoBianma("copy-lock");
|
ElnTemplateFormulaBindingEntity copied = f.formulaBinding(
|
"copied-formula-lock", "template-1", "PENDING");
|
copied.setLaiyuanGongshiBangdingId("source-formula-lock");
|
copied.setCaozuoBianma("copy-lock:source-formula-lock");
|
copied.setDaoqiShijian(null);
|
store.bindings.put(copied.getId(), copied);
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("copy-lock");
|
confirm.setExpectedVersions(Collections.singletonMap(copied.getId(), 1));
|
confirm.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
|
f.service.confirmVersionCopy(f.authorization(), confirm);
|
|
assertEquals(1, store.lockCalls.get());
|
assertEquals("ACTIVE", store.bindings.get(copied.getId()).getBangdingZhuangtai());
|
}
|
|
@Test
|
public void activateRejectsControlRoleMismatchWithoutChangingPendingState() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", "role-activate", map("amount", "input"), "result"));
|
ElnFormulaForms.ActivateFormulaBinding activate = new ElnFormulaForms.ActivateFormulaBinding();
|
activate.setOperationCode("role-activate");
|
activate.setExpectedVersion(1);
|
ElnFormulaForms.ControlSnapshot result = f.control("result", "sdtContentLocked");
|
result.setControlRole("REFERENCE");
|
activate.setControls(Arrays.asList(result,
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("FORMULA_RESULT_LOCK_MISMATCH", () -> f.service.activate(
|
f.authorization(), reserved.getId(), activate));
|
|
assertEquals("PENDING", f.store.binding(reserved.getId()).getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_ACTIVATE"));
|
}
|
|
@Test
|
public void activateRejectsLegacySnapshotWithoutControlRole() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", "legacy-controls", map("amount", "input"), "result"));
|
ElnFormulaForms.ActivateFormulaBinding activate = new ElnFormulaForms.ActivateFormulaBinding();
|
activate.setOperationCode("legacy-controls");
|
activate.setExpectedVersion(1);
|
ElnFormulaForms.ControlSnapshot result = f.control("result", "sdtContentLocked");
|
ElnFormulaForms.ControlSnapshot reference = f.control("result-ref", "sdtContentLocked");
|
result.setControlRole(null);
|
reference.setControlRole(null);
|
activate.setControls(Arrays.asList(result, reference));
|
|
expectFailure("FORMULA_RESULT_LOCK_MISMATCH", () -> f.service.activate(
|
f.authorization(), reserved.getId(), activate));
|
|
assertEquals("PENDING", f.store.binding(reserved.getId()).getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_ACTIVATE"));
|
}
|
|
@Test
|
public void reserveReturnsConcurrentWinnerWithSameRequestWithoutAudit() {
|
Fixture f = new Fixture();
|
ElnTemplateFormulaBindingEntity winner = f.formulaBinding(
|
"concurrent-formula", "template-1", "PENDING");
|
winner.setCaozuoBianma("concurrent-op");
|
winner.setDaoqiShijian(new Date(System.currentTimeMillis() + 60_000L));
|
f.store.concurrentInsert = winner;
|
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", " concurrent-op ",
|
map("amount", "input"), "result"));
|
|
assertEquals("concurrent-formula", reserved.getId());
|
assertEquals("PENDING", reserved.getStatus());
|
assertEquals(1, f.store.bindings.size());
|
assertSame(winner, f.store.binding("concurrent-formula"));
|
assertEquals(2, f.store.findByOperationCalls);
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_RESERVE"));
|
}
|
|
@Test
|
public void reserveRejectsConcurrentWinnerWithDifferentRequestWithoutAudit() {
|
Fixture f = new Fixture();
|
ElnTemplateFormulaBindingEntity winner = f.formulaBinding(
|
"concurrent-formula", "template-1", "PENDING");
|
winner.setCaozuoBianma("concurrent-mismatch");
|
winner.setJieguoBangdingId("other-result");
|
f.store.concurrentInsert = winner;
|
|
expectFailure("操作码", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "concurrent-mismatch",
|
map("amount", "input"), "result")));
|
|
assertEquals(1, f.store.bindings.size());
|
assertSame(winner, f.store.binding("concurrent-formula"));
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_RESERVE"));
|
}
|
|
@Test
|
public void reservePropagatesNonUniqueDatabaseFailureUnchanged() {
|
Fixture f = new Fixture();
|
DataAccessResourceFailureException failure =
|
new DataAccessResourceFailureException("database unavailable");
|
f.store.insertFailure = failure;
|
|
try {
|
f.service.reserve(f.authorization(), f.request("formula-current", "db-failure",
|
map("amount", "input"), "result"));
|
fail("数据库异常必须原样冒泡");
|
} catch (DataAccessResourceFailureException ex) {
|
assertSame(failure, ex);
|
}
|
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_RESERVE"));
|
}
|
|
@Test
|
public void rejectsIncompleteSlotsWrongTypesInvalidResultAndDisabledFormula() {
|
Fixture f = new Fixture();
|
expectFailure("参数", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "missing", Collections.emptyMap(), "result")));
|
|
f.fieldVersions.get("input-version").setShujuLeixing("TEXT");
|
expectFailure("类型", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "wrong-type", map("amount", "input"), "result")));
|
f.fieldVersions.get("input-version").setShujuLeixing("DECIMAL");
|
|
f.fieldVersions.get("result-version").setPiciMoshi("BATCH_SHARED");
|
expectFailure("结果", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "wrong-result", map("amount", "input"), "result")));
|
f.fieldVersions.get("result-version").setPiciMoshi("RECORD_PRIVATE");
|
|
f.definition.setBizEnabled("disabled");
|
expectFailure("停用", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "disabled", map("amount", "input"), "result")));
|
}
|
|
@Test
|
public void rejectsMutualExclusionGroupMemberAsFormulaResult() {
|
Fixture f = new Fixture();
|
f.store.fieldBindings.get("result").setCheckboxGroupCode("result_choice");
|
|
expectFailure("选择组", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "grouped-result",
|
map("amount", "input"), "result")));
|
}
|
|
@Test
|
public void cancelRetryIsIdempotentAndAuditedOnce() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", "cancel-op", map("amount", "input"), "result"));
|
ElnFormulaForms.CancelFormulaBinding cancel = new ElnFormulaForms.CancelFormulaBinding();
|
cancel.setOperationCode("cancel-op"); cancel.setExpectedVersion(1); cancel.setReason("插件失败");
|
|
f.service.cancel(f.authorization(), reserved.getId(), cancel);
|
f.service.cancel(f.authorization(), reserved.getId(), cancel);
|
|
assertEquals("CANCELLED", f.store.binding(reserved.getId()).getBangdingZhuangtai());
|
assertEquals(1, f.auditCount("ELN_TEMPLATE_FORMULA_CANCEL"));
|
}
|
|
@Test
|
public void rejectsHistoricalPublishedForManualReserveButAllowsPreRelease() {
|
Fixture f = new Fixture();
|
f.addFormulaVersion("formula-old", "PUBLISHED", "DECIMAL");
|
expectFailure("历史", () -> f.service.reserve(f.authorization(),
|
f.request("formula-old", "old", map("amount", "input"), "result")));
|
|
f.addFormulaVersion("formula-pre", "PRE_RELEASE", "DECIMAL");
|
assertEquals("formula-pre", f.service.reserve(f.authorization(),
|
f.request("formula-pre", "pre", map("amount", "input"), "result")).getFormulaVersionId());
|
}
|
|
@Test
|
public void rejectsCollectionAndSelfDependencyAndCrossTemplateId() {
|
Fixture f = new Fixture();
|
f.versions.get("formula-current").setJieguoLeixing("DECIMAL_LIST");
|
expectFailure("UNSUPPORTED_COLLECTION_BINDING", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "list", map("amount", "input"), "result")));
|
f.versions.get("formula-current").setJieguoLeixing("DECIMAL");
|
|
expectFailure("自身", () -> f.service.reserve(f.authorization(),
|
f.request("formula-current", "self", map("amount", "result"), "result")));
|
|
ElnTemplateFormulaBindingEntity foreign = f.formulaBinding("foreign", "template-2", "PENDING");
|
f.store.bindings.put(foreign.getId(), foreign);
|
ElnFormulaForms.CancelFormulaBinding cancel = new ElnFormulaForms.CancelFormulaBinding();
|
cancel.setOperationCode("foreign-op");
|
cancel.setExpectedVersion(1);
|
cancel.setReason("cancel");
|
expectFailure("模板", () -> f.service.cancel(f.authorization(), "foreign", cancel));
|
}
|
|
@Test
|
public void acceptsDecimalListMappedToRepeatColumnWithPrimaryResult() {
|
Fixture f = new Fixture();
|
f.store.slots.get("formula-current").get(0).setShujuLeixing("DECIMAL_LIST");
|
f.addRepeatField("repeat-input", "repeat-input-version", "group-a");
|
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", "aggregate", map("amount", "repeat-input"),
|
"result"));
|
|
assertEquals("PENDING", reserved.getStatus());
|
}
|
|
@Test
|
public void acceptsScalarRepeatFormulaWithinOneGroupAndRejectsCrossGroup() {
|
Fixture valid = new Fixture();
|
valid.addRepeatField("repeat-input", "repeat-input-version", "group-a");
|
valid.addRepeatField("repeat-result", "repeat-result-version", "group-a");
|
ElnFormulaForms.TemplateBinding request = valid.request("formula-current",
|
"row-formula", map("amount", "repeat-input"), "repeat-result");
|
request.setJieguoSuoKuaizhao(Collections.singletonList(
|
valid.control("repeat-result", "")));
|
|
assertEquals("PENDING", valid.service.reserve(
|
valid.authorization(), request).getStatus());
|
|
Fixture invalid = new Fixture();
|
invalid.addRepeatField("repeat-input", "repeat-input-version", "group-a");
|
invalid.addRepeatField("repeat-result", "repeat-result-version", "group-b");
|
ElnFormulaForms.TemplateBinding crossGroup = invalid.request("formula-current",
|
"row-cross-group", map("amount", "repeat-input"), "repeat-result");
|
crossGroup.setJieguoSuoKuaizhao(Collections.singletonList(
|
invalid.control("repeat-result", "")));
|
expectFailure("同一重复组", () -> invalid.service.reserve(
|
invalid.authorization(), crossGroup));
|
}
|
|
@Test
|
public void versionCopyParticipantRemapsBindingsAndIsIdempotent() {
|
CopyStore store = new CopyStore();
|
ElnTemplateVersionEntity target = new ElnTemplateVersionEntity();
|
target.setId("target"); target.setFTenantId("tenant-1");
|
target.setBizStatus("DRAFT");
|
target.setShengbanCaozuoBianma("upgrade-1");
|
store.target = target;
|
ElnTemplateFormulaBindingEntity source = new ElnTemplateFormulaBindingEntity();
|
source.setId("source-formula"); source.setFTenantId("tenant-1");
|
source.setMubanId("source"); source.setGongshiDingyiId("formula-def");
|
source.setGongshiBanbenId("historical-published");
|
source.setCanshuYingshe(map("amount", "old-input"));
|
source.setJieguoBangdingId("old-result"); source.setBangdingZhuangtai("ACTIVE");
|
store.source.add(source);
|
FormulaTemplateVersionCopyParticipant participant =
|
new FormulaTemplateVersionCopyParticipant(store, () -> "copied-formula");
|
Map<String, String> fieldMap = new LinkedHashMap<>();
|
fieldMap.put("old-input", "new-input"); fieldMap.put("old-result", "new-result");
|
|
participant.afterBindingsActivated("tenant-1", "source", "target", fieldMap);
|
participant.afterBindingsActivated("tenant-1", "source", "target", fieldMap);
|
|
assertEquals(1, store.inserted.size());
|
ElnTemplateFormulaBindingEntity copied = store.inserted.get(0);
|
assertEquals(map("amount", "new-input"), copied.getCanshuYingshe());
|
assertEquals("new-result", copied.getJieguoBangdingId());
|
assertEquals("historical-published", copied.getGongshiBanbenId());
|
assertEquals("upgrade-1:source-formula", copied.getCaozuoBianma());
|
assertEquals("PENDING", copied.getBangdingZhuangtai());
|
assertEquals(null, copied.getDaoqiShijian());
|
}
|
|
@Test
|
public void versionCopyAcceptsConcurrentWinnerWithExactCopySemantics() {
|
CopyStore store = new CopyStore();
|
Map<String, String> fieldMap = store.prepareCopy();
|
ElnTemplateFormulaBindingEntity winner = store.concurrentWinner(fieldMap);
|
store.concurrentInsert = winner;
|
|
new FormulaTemplateVersionCopyParticipant(store, () -> "losing-copy")
|
.afterBindingsActivated("tenant-1", "source", "target", fieldMap);
|
|
assertEquals(1, store.inserted.size());
|
assertSame(winner, store.inserted.get(0));
|
assertEquals("PENDING", winner.getBangdingZhuangtai());
|
assertEquals(null, winner.getDaoqiShijian());
|
assertEquals(2, store.findByOperationCalls);
|
}
|
|
@Test
|
public void versionCopyRejectsConcurrentWinnerWithDifferentCopySemantics() {
|
CopyStore store = new CopyStore();
|
Map<String, String> fieldMap = store.prepareCopy();
|
ElnTemplateFormulaBindingEntity winner = store.concurrentWinner(fieldMap);
|
winner.setCanshuYingshe(map("amount", "wrong-input"));
|
store.concurrentInsert = winner;
|
|
expectFailure("操作码", () -> new FormulaTemplateVersionCopyParticipant(
|
store, () -> "losing-copy").afterBindingsActivated(
|
"tenant-1", "source", "target", fieldMap));
|
|
assertEquals(1, store.inserted.size());
|
assertSame(winner, store.inserted.get(0));
|
}
|
|
@Test
|
public void versionCopyPropagatesNonUniqueDatabaseFailureUnchanged() {
|
CopyStore store = new CopyStore();
|
Map<String, String> fieldMap = store.prepareCopy();
|
DataAccessResourceFailureException failure =
|
new DataAccessResourceFailureException("database unavailable");
|
store.insertFailure = failure;
|
|
try {
|
new FormulaTemplateVersionCopyParticipant(store, () -> "copy-failure")
|
.afterBindingsActivated("tenant-1", "source", "target", fieldMap);
|
fail("数据库异常必须原样冒泡");
|
} catch (DataAccessResourceFailureException ex) {
|
assertSame(failure, ex);
|
}
|
|
assertTrue(store.inserted.isEmpty());
|
}
|
|
@Test
|
public void versionCopyParticipantAllowsPreReleaseFormulaForDraftTarget() {
|
CopyStore store = new CopyStore();
|
Map<String, String> fieldMap = store.prepareCopy();
|
store.formulaVersion.setBizStatus("PRE_RELEASE");
|
|
new FormulaTemplateVersionCopyParticipant(store, () -> "copied-formula")
|
.afterBindingsActivated("tenant-1", "source", "target", fieldMap);
|
|
assertEquals(1, store.inserted.size());
|
assertEquals("historical-published", store.inserted.get(0).getGongshiBanbenId());
|
assertEquals("PENDING", store.inserted.get(0).getBangdingZhuangtai());
|
}
|
|
@Test
|
public void versionCopyParticipantRejectsDraftFormulaVersion() {
|
CopyStore store = new CopyStore();
|
ElnTemplateVersionEntity target = new ElnTemplateVersionEntity();
|
target.setId("target"); target.setFTenantId("tenant-1");
|
target.setBizStatus("DRAFT");
|
target.setShengbanCaozuoBianma("upgrade-1");
|
store.target = target;
|
store.formulaVersion.setBizStatus("DRAFT");
|
ElnTemplateFormulaBindingEntity source = new ElnTemplateFormulaBindingEntity();
|
source.setId("source-formula"); source.setFTenantId("tenant-1");
|
source.setMubanId("source"); source.setGongshiDingyiId("formula-def");
|
source.setGongshiBanbenId("historical-published");
|
source.setCanshuYingshe(map("amount", "old-input"));
|
source.setJieguoBangdingId("old-result"); source.setBangdingZhuangtai("ACTIVE");
|
store.source.add(source);
|
Map<String, String> fieldMap = new LinkedHashMap<>();
|
fieldMap.put("old-input", "new-input"); fieldMap.put("old-result", "new-result");
|
|
expectFailure("已发布", () -> new FormulaTemplateVersionCopyParticipant(
|
store, () -> "copied-formula").afterBindingsActivated(
|
"tenant-1", "source", "target", fieldMap));
|
|
assertTrue(store.inserted.isEmpty());
|
}
|
|
@Test
|
public void upgradeAndRemoveConfirmAreCasIdempotentAndAuditedOnce() {
|
Fixture upgrade = new Fixture();
|
ElnFormulaVos.TemplateBinding active = upgrade.activeMapping("reserve-upgrade");
|
upgrade.addFormulaVersion("formula-pre", "PRE_RELEASE", "DECIMAL");
|
ElnFormulaForms.TemplateBinding target = upgrade.request("formula-pre", "upgrade-op",
|
map("amount", "input"), "result");
|
ElnFormulaVos.MappingCheck checked = upgrade.service.checkUpgrade(
|
upgrade.authorization(), active.getId(), target);
|
ElnFormulaForms.UpgradeMappingConfirm confirm = new ElnFormulaForms.UpgradeMappingConfirm();
|
confirm.setOperationCode("upgrade-op"); confirm.setExpectedVersion(
|
checked.getExpectedVersions().get(active.getId())); confirm.setTarget(target);
|
confirm.setControls(Arrays.asList(upgrade.control("result", "sdtContentLocked"),
|
upgrade.control("result-ref", "sdtContentLocked")));
|
|
upgrade.service.confirmUpgrade(upgrade.authorization(), active.getId(), confirm);
|
upgrade.service.confirmUpgrade(upgrade.authorization(), active.getId(), confirm);
|
|
assertEquals("formula-pre", upgrade.store.binding(active.getId()).getGongshiBanbenId());
|
assertEquals(1, upgrade.auditCount("ELN_TEMPLATE_FORMULA_UPGRADE"));
|
|
Fixture remove = new Fixture();
|
ElnFormulaVos.TemplateBinding removable = remove.activeMapping("reserve-remove");
|
ElnFormulaVos.MappingCheck removal = remove.service.checkRemove(
|
remove.authorization(), removable.getId());
|
ElnFormulaForms.RemoveMappingConfirm removeConfirm = new ElnFormulaForms.RemoveMappingConfirm();
|
removeConfirm.setOperationCode(removal.getOperationCode());
|
removeConfirm.setExpectedVersion(removal.getExpectedVersions().get(removable.getId()));
|
removeConfirm.setControls(Arrays.asList(remove.control("result", null),
|
remove.control("result-ref", null)));
|
|
remove.service.confirmRemove(remove.authorization(), removable.getId(), removeConfirm);
|
remove.service.confirmRemove(remove.authorization(), removable.getId(), removeConfirm);
|
|
assertEquals("REMOVED", remove.store.binding(removable.getId()).getBangdingZhuangtai());
|
assertEquals(1, remove.auditCount("ELN_TEMPLATE_FORMULA_REMOVE"));
|
}
|
|
@Test
|
public void versionCopyConfirmRequiresExactSetAndRetriesWithoutSecondAudit() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("upgrade-copy");
|
ElnTemplateFormulaBindingEntity copied = f.formulaBinding(
|
"copied-formula", "template-1", "PENDING");
|
copied.setLaiyuanGongshiBangdingId("source-formula");
|
copied.setCaozuoBianma("upgrade-copy:source-formula");
|
copied.setDaoqiShijian(null);
|
f.store.bindings.put(copied.getId(), copied);
|
ElnFormulaForms.FormulaVersionCopyConfirm incomplete =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
incomplete.setUpgradeOperationCode("upgrade-copy");
|
incomplete.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
expectFailure("集合", () -> f.service.confirmVersionCopy(f.authorization(), incomplete));
|
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("upgrade-copy");
|
confirm.setExpectedVersions(Collections.singletonMap("copied-formula", 1));
|
confirm.setControls(incomplete.getControls());
|
f.service.confirmVersionCopy(f.authorization(), confirm);
|
f.service.confirmVersionCopy(f.authorization(), confirm);
|
|
assertEquals("ACTIVE", copied.getBangdingZhuangtai());
|
assertEquals(1, f.auditCount("ELN_TEMPLATE_FORMULA_VERSION_COPY_CONFIRM"));
|
}
|
|
@Test
|
public void versionCopyConfirmValidatesTheUnionOfAllResultControls() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("upgrade-copy-many");
|
ElnTemplateFormulaBindingEntity first = f.formulaBinding(
|
"copied-formula-1", "template-1", "PENDING");
|
first.setLaiyuanGongshiBangdingId("source-formula-1");
|
first.setCaozuoBianma("upgrade-copy-many:source-formula-1");
|
first.setDaoqiShijian(null);
|
f.store.bindings.put(first.getId(), first);
|
f.addField("result-2", "result-version-2", "DECIMAL", manualBinding());
|
f.addFieldBinding("result-2-ref", "result-version-2", "REFERENCE", "result-2");
|
ElnTemplateFormulaBindingEntity second = f.formulaBinding(
|
"copied-formula-2", "template-1", "PENDING");
|
second.setJieguoBangdingId("result-2");
|
second.setLaiyuanGongshiBangdingId("source-formula-2");
|
second.setCaozuoBianma("upgrade-copy-many:source-formula-2");
|
second.setDaoqiShijian(null);
|
f.store.bindings.put(second.getId(), second);
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("upgrade-copy-many");
|
Map<String, Integer> expected = new LinkedHashMap<>();
|
expected.put(first.getId(), 1); expected.put(second.getId(), 1);
|
confirm.setExpectedVersions(expected);
|
confirm.setControls(Arrays.asList(
|
f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked"),
|
f.control("result-2", "sdtContentLocked"),
|
f.control("result-2-ref", "sdtContentLocked")));
|
|
f.service.confirmVersionCopy(f.authorization(), confirm);
|
|
assertEquals("ACTIVE", first.getBangdingZhuangtai());
|
assertEquals("ACTIVE", second.getBangdingZhuangtai());
|
assertEquals(2, f.auditCount("ELN_TEMPLATE_FORMULA_VERSION_COPY_CONFIRM"));
|
}
|
|
@Test
|
public void versionCopyConfirmRejectsMixedPendingAndActiveState() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("upgrade-copy-mixed");
|
ElnTemplateFormulaBindingEntity pending = f.formulaBinding(
|
"copied-formula-pending", "template-1", "PENDING");
|
pending.setLaiyuanGongshiBangdingId("source-formula-pending");
|
pending.setCaozuoBianma("upgrade-copy-mixed:source-formula-pending");
|
pending.setDaoqiShijian(null);
|
f.store.bindings.put(pending.getId(), pending);
|
ElnTemplateFormulaBindingEntity active = f.formulaBinding(
|
"copied-formula-active", "template-1", "ACTIVE");
|
active.setLaiyuanGongshiBangdingId("source-formula-active");
|
active.setCaozuoBianma("upgrade-copy-mixed:source-formula-active");
|
active.setDaoqiShijian(null);
|
active.setFVersion(2);
|
f.store.bindings.put(active.getId(), active);
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("upgrade-copy-mixed");
|
confirm.setExpectedVersions(Collections.singletonMap(pending.getId(), 1));
|
confirm.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("混合", () -> f.service.confirmVersionCopy(f.authorization(), confirm));
|
|
assertEquals("PENDING", pending.getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_VERSION_COPY_CONFIRM"));
|
}
|
|
@Test
|
public void versionCopyConfirmRejectsMalformedCopiedOperationWithoutPartialActivation() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("upgrade-copy-strict");
|
ElnTemplateFormulaBindingEntity valid = f.formulaBinding(
|
"copied-formula-valid", "template-1", "PENDING");
|
valid.setLaiyuanGongshiBangdingId("source-formula-valid");
|
valid.setCaozuoBianma("upgrade-copy-strict:source-formula-valid");
|
valid.setDaoqiShijian(null);
|
f.store.bindings.put(valid.getId(), valid);
|
ElnTemplateFormulaBindingEntity malformed = f.formulaBinding(
|
"copied-formula-malformed", "template-1", "PENDING");
|
malformed.setLaiyuanGongshiBangdingId("source-formula-malformed");
|
malformed.setCaozuoBianma("upgrade-copy-strict:wrong-source");
|
malformed.setDaoqiShijian(null);
|
f.store.bindings.put(malformed.getId(), malformed);
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("upgrade-copy-strict");
|
confirm.setExpectedVersions(Collections.singletonMap(valid.getId(), 1));
|
confirm.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("操作码", () -> f.service.confirmVersionCopy(f.authorization(), confirm));
|
|
assertEquals("PENDING", valid.getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_VERSION_COPY_CONFIRM"));
|
}
|
|
@Test
|
public void versionCopyConfirmRejectsCopiedPendingWithExpiry() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("upgrade-copy-expiry");
|
ElnTemplateFormulaBindingEntity copied = f.formulaBinding(
|
"copied-formula-expiry", "template-1", "PENDING");
|
copied.setLaiyuanGongshiBangdingId("source-formula-expiry");
|
copied.setCaozuoBianma("upgrade-copy-expiry:source-formula-expiry");
|
copied.setDaoqiShijian(new Date(System.currentTimeMillis() + 60_000L));
|
f.store.bindings.put(copied.getId(), copied);
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm =
|
new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("upgrade-copy-expiry");
|
confirm.setExpectedVersions(Collections.singletonMap(copied.getId(), 1));
|
confirm.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("到期", () -> f.service.confirmVersionCopy(f.authorization(), confirm));
|
|
assertEquals("PENDING", copied.getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_VERSION_COPY_CONFIRM"));
|
}
|
|
@Test
|
public void upgradeCheckRejectsOperationCodeOwnedByAnotherMapping() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding source = f.activeMapping("upgrade-source-op");
|
ElnTemplateFormulaBindingEntity occupied = f.formulaBinding(
|
"occupied-formula", "template-1", "ACTIVE");
|
occupied.setCaozuoBianma("occupied-upgrade-op");
|
f.store.bindings.put(occupied.getId(), occupied);
|
ElnFormulaForms.TemplateBinding target = f.request("formula-current",
|
" occupied-upgrade-op ", map("amount", "input"), "result");
|
|
expectFailure("操作码", () -> f.service.checkUpgrade(
|
f.authorization(), source.getId(), target));
|
}
|
|
@Test
|
public void upgradeNormalizesOperationCodeForCheckConfirmAndRetry() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding source = f.activeMapping("upgrade-trim-source");
|
ElnFormulaForms.TemplateBinding target = f.request("formula-current",
|
" upgrade-trimmed ", map("amount", "input"), "result");
|
|
ElnFormulaVos.MappingCheck checked = f.service.checkUpgrade(
|
f.authorization(), source.getId(), target);
|
|
assertEquals("upgrade-trimmed", checked.getOperationCode());
|
ElnFormulaForms.UpgradeMappingConfirm confirm =
|
new ElnFormulaForms.UpgradeMappingConfirm();
|
confirm.setOperationCode(" upgrade-trimmed ");
|
confirm.setExpectedVersion(2);
|
confirm.setTarget(target);
|
confirm.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
f.service.confirmUpgrade(f.authorization(), source.getId(), confirm);
|
f.service.confirmUpgrade(f.authorization(), source.getId(), confirm);
|
|
assertEquals("upgrade-trimmed",
|
f.store.binding(source.getId()).getCaozuoBianma());
|
assertEquals(1, f.auditCount("ELN_TEMPLATE_FORMULA_UPGRADE"));
|
}
|
|
@Test
|
public void removeCheckReportsAndConfirmRejectsDownstreamFormula() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding upstream = f.activeMapping("upstream");
|
f.addField("result-2", "result-version-2", "DECIMAL", manualBinding());
|
ElnTemplateFormulaBindingEntity downstream = f.formulaBinding(
|
"downstream", "template-1", "ACTIVE");
|
downstream.setCanshuYingshe(map("amount", "result"));
|
downstream.setJieguoBangdingId("result-2");
|
f.store.bindings.put(downstream.getId(), downstream);
|
|
ElnFormulaVos.MappingCheck check = f.service.checkRemove(
|
f.authorization(), upstream.getId());
|
|
assertEquals(Collections.singletonList("downstream"),
|
check.getDownstreamFormulaBindingIds());
|
ElnFormulaForms.RemoveMappingConfirm confirm = new ElnFormulaForms.RemoveMappingConfirm();
|
confirm.setOperationCode(check.getOperationCode());
|
confirm.setExpectedVersion(check.getExpectedVersions().get(upstream.getId()));
|
confirm.setControls(Arrays.asList(f.control("result", null),
|
f.control("result-ref", null)));
|
expectFailure("下游", () -> f.service.confirmRemove(
|
f.authorization(), upstream.getId(), confirm));
|
assertEquals("ACTIVE", f.store.binding(upstream.getId()).getBangdingZhuangtai());
|
}
|
|
@Test
|
public void upgradeValidatesLocksForTheTargetResultBinding() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding active = f.activeMapping("reserve-upgrade-result");
|
f.addFormulaVersion("formula-pre", "PRE_RELEASE", "DECIMAL");
|
f.addField("result-2", "result-version-2", "DECIMAL", manualBinding());
|
f.addFieldBinding("result-2-ref", "result-version-2", "REFERENCE", "result-2");
|
ElnFormulaForms.TemplateBinding target = f.request("formula-pre", "upgrade-result-op",
|
map("amount", "input"), "result-2");
|
ElnFormulaVos.MappingCheck checked = f.service.checkUpgrade(
|
f.authorization(), active.getId(), target);
|
ElnFormulaForms.UpgradeMappingConfirm confirm = new ElnFormulaForms.UpgradeMappingConfirm();
|
confirm.setOperationCode("upgrade-result-op");
|
confirm.setExpectedVersion(checked.getExpectedVersions().get(active.getId()));
|
confirm.setTarget(target);
|
confirm.setControls(Arrays.asList(f.control("result-2", "sdtContentLocked"),
|
f.control("result-2-ref", "sdtContentLocked")));
|
|
f.service.confirmUpgrade(f.authorization(), active.getId(), confirm);
|
|
assertEquals("result-2", f.store.binding(active.getId()).getJieguoBangdingId());
|
}
|
|
@Test
|
public void removeConfirmRejectsUnlockedSnapshotWithWrongControlTag() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding active = f.activeMapping("reserve-remove-tag");
|
ElnFormulaVos.MappingCheck checked = f.service.checkRemove(
|
f.authorization(), active.getId());
|
ElnFormulaForms.RemoveMappingConfirm confirm = new ElnFormulaForms.RemoveMappingConfirm();
|
confirm.setOperationCode(checked.getOperationCode());
|
confirm.setExpectedVersion(checked.getExpectedVersions().get(active.getId()));
|
ElnFormulaForms.ControlSnapshot primary = f.control("result", null);
|
primary.setControlTag("eln.field.forged");
|
confirm.setControls(Arrays.asList(primary, f.control("result-ref", null)));
|
|
expectFailure("FORMULA_RESULT_LOCK_MISMATCH", () -> f.service.confirmRemove(
|
f.authorization(), active.getId(), confirm));
|
|
assertEquals("ACTIVE", f.store.binding(active.getId()).getBangdingZhuangtai());
|
}
|
|
@Test
|
public void removeConfirmRejectsControlRoleMismatchWithoutChangingActiveState() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding active = f.activeMapping("reserve-remove-role");
|
ElnFormulaVos.MappingCheck checked = f.service.checkRemove(
|
f.authorization(), active.getId());
|
ElnFormulaForms.RemoveMappingConfirm confirm = new ElnFormulaForms.RemoveMappingConfirm();
|
confirm.setOperationCode(checked.getOperationCode());
|
confirm.setExpectedVersion(checked.getExpectedVersions().get(active.getId()));
|
ElnFormulaForms.ControlSnapshot result = f.control("result", null);
|
result.setControlRole("REFERENCE");
|
confirm.setControls(Arrays.asList(result, f.control("result-ref", null)));
|
|
expectFailure("FORMULA_RESULT_LOCK_MISMATCH", () -> f.service.confirmRemove(
|
f.authorization(), active.getId(), confirm));
|
|
assertEquals("ACTIVE", f.store.binding(active.getId()).getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_REMOVE"));
|
}
|
|
@Test
|
public void versionCopyConfirmRejectsControlRoleMismatchWithoutActivation() {
|
Fixture f = new Fixture();
|
f.store.template.setShengbanCaozuoBianma("upgrade-copy-role");
|
ElnTemplateFormulaBindingEntity copied = f.formulaBinding(
|
"copied-formula-role", "template-1", "PENDING");
|
copied.setLaiyuanGongshiBangdingId("source-formula-role");
|
copied.setCaozuoBianma("upgrade-copy-role:source-formula-role");
|
copied.setDaoqiShijian(null);
|
f.store.bindings.put(copied.getId(), copied);
|
|
ElnFormulaForms.FormulaVersionCopyConfirm confirm = new ElnFormulaForms.FormulaVersionCopyConfirm();
|
confirm.setUpgradeOperationCode("upgrade-copy-role");
|
confirm.setExpectedVersions(Collections.singletonMap(copied.getId(), 1));
|
ElnFormulaForms.ControlSnapshot result = f.control("result", "sdtContentLocked");
|
result.setControlRole("REFERENCE");
|
confirm.setControls(Arrays.asList(result,
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("FORMULA_RESULT_LOCK_MISMATCH", () -> f.service.confirmVersionCopy(
|
f.authorization(), confirm));
|
|
assertEquals("PENDING", copied.getBangdingZhuangtai());
|
assertEquals(0, f.auditCount("ELN_TEMPLATE_FORMULA_VERSION_COPY_CONFIRM"));
|
}
|
|
@Test
|
public void confirmUpgradeRejectsNonActiveSourceMapping() {
|
Fixture f = new Fixture();
|
ElnFormulaVos.TemplateBinding reserved = f.service.reserve(f.authorization(),
|
f.request("formula-current", "cancel-before-upgrade",
|
map("amount", "input"), "result"));
|
ElnFormulaForms.CancelFormulaBinding cancel = new ElnFormulaForms.CancelFormulaBinding();
|
cancel.setOperationCode("cancel-before-upgrade");
|
cancel.setExpectedVersion(reserved.getVersion());
|
f.service.cancel(f.authorization(), reserved.getId(), cancel);
|
f.addFormulaVersion("formula-pre", "PRE_RELEASE", "DECIMAL");
|
ElnFormulaForms.TemplateBinding target = f.request("formula-pre", "invalid-upgrade",
|
map("amount", "input"), "result");
|
ElnFormulaForms.UpgradeMappingConfirm confirm = new ElnFormulaForms.UpgradeMappingConfirm();
|
confirm.setOperationCode("invalid-upgrade");
|
confirm.setExpectedVersion(2);
|
confirm.setTarget(target);
|
confirm.setControls(Arrays.asList(f.control("result", "sdtContentLocked"),
|
f.control("result-ref", "sdtContentLocked")));
|
|
expectFailure("状态", () -> f.service.confirmUpgrade(
|
f.authorization(), reserved.getId(), confirm));
|
|
assertEquals("CANCELLED", f.store.binding(reserved.getId()).getBangdingZhuangtai());
|
}
|
|
private static void expectFailure(String message, Runnable operation) {
|
try {
|
operation.run();
|
fail("应拒绝: " + message);
|
} catch (DataException ex) {
|
assertTrue(ex.getMessage(), ex.getMessage().contains(message));
|
}
|
}
|
|
private static Map<String, String> map(String key, String value) {
|
Map<String, String> result = new LinkedHashMap<>();
|
result.put(key, value);
|
return result;
|
}
|
|
private static String operationOfLength(int length) {
|
StringBuilder result = new StringBuilder(length);
|
for (int index = 0; index < length; index++) result.append('x');
|
return result.toString();
|
}
|
|
private static Callable<String> activation(Fixture fixture,
|
ConcurrentGraphStore store, String bindingId,
|
String operationCode, String resultBindingId) {
|
return () -> {
|
ElnFormulaForms.ActivateFormulaBinding form =
|
new ElnFormulaForms.ActivateFormulaBinding();
|
form.setOperationCode(operationCode);
|
form.setExpectedVersion(1);
|
form.setControls(Collections.singletonList(
|
fixture.control(resultBindingId, "sdtContentLocked")));
|
try {
|
fixture.service.activate(fixture.authorization(), bindingId, form);
|
store.commitTransaction();
|
return "SUCCESS";
|
} catch (DataException ex) {
|
store.rollbackTransaction();
|
return ex.getMessage();
|
} catch (RuntimeException | Error ex) {
|
store.rollbackTransaction();
|
throw ex;
|
}
|
};
|
}
|
|
private static Callable<String> upgrade(Fixture fixture,
|
ConcurrentGraphStore store, String bindingId, String operationCode,
|
String parameterBindingId, String resultBindingId) {
|
return () -> {
|
ElnFormulaForms.TemplateBinding target = fixture.request(
|
"formula-current", operationCode,
|
map("amount", parameterBindingId), resultBindingId);
|
ElnFormulaForms.UpgradeMappingConfirm form =
|
new ElnFormulaForms.UpgradeMappingConfirm();
|
form.setOperationCode(operationCode);
|
form.setExpectedVersion(1);
|
form.setTarget(target);
|
form.setControls(Collections.singletonList(
|
fixture.control(resultBindingId, "sdtContentLocked")));
|
try {
|
fixture.service.confirmUpgrade(fixture.authorization(), bindingId, form);
|
store.commitTransaction();
|
return "SUCCESS";
|
} catch (DataException ex) {
|
store.rollbackTransaction();
|
return ex.getMessage();
|
} catch (RuntimeException | Error ex) {
|
store.rollbackTransaction();
|
throw ex;
|
}
|
};
|
}
|
|
private static final class Fixture {
|
private final MemoryStore store;
|
private final List<AuditEventDTO> audits =
|
Collections.synchronizedList(new ArrayList<>());
|
private final ElnFormulaDefEntity definition = new ElnFormulaDefEntity();
|
private final Map<String, ElnFormulaVersionEntity> versions;
|
private final Map<String, ElnFieldVersionEntity> fieldVersions;
|
private final ElnTemplateFormulaBindingServiceImpl service;
|
|
private Fixture() {
|
this(new MemoryStore());
|
}
|
|
private Fixture(MemoryStore store) {
|
this.store = store;
|
this.versions = store.versions;
|
this.fieldVersions = store.fieldVersions;
|
ElnTemplateVersionEntity template = new ElnTemplateVersionEntity();
|
template.setId("template-1");
|
template.setFTenantId("tenant-1");
|
template.setBizStatus("DRAFT");
|
template.setBizEnabled("enabled");
|
store.template = template;
|
|
definition.setId("formula-def");
|
definition.setFTenantId("tenant-1");
|
definition.setBizEnabled("enabled");
|
definition.setDangqianGongshiBanbenId("formula-current");
|
store.definition = definition;
|
addFormulaVersion("formula-current", "PUBLISHED", "DECIMAL");
|
ElnFormulaParameterSlotEntity slot = new ElnFormulaParameterSlotEntity();
|
slot.setId("slot-1");
|
slot.setFTenantId("tenant-1");
|
slot.setGongshiBanbenId("formula-current");
|
slot.setCanshuBianma("amount");
|
slot.setShujuLeixing("DECIMAL");
|
store.slots.put("formula-current", Collections.singletonList(slot));
|
|
addField("input", "input-version", "DECIMAL", manualBinding());
|
addField("result", "result-version", "DECIMAL", manualBinding());
|
addFieldBinding("result-ref", "result-version", "REFERENCE", "result");
|
service = new ElnTemplateFormulaBindingServiceImpl(store, new FormulaDependencyGraph(),
|
() -> "formula-binding-1", audits::add, 30L);
|
}
|
|
private void addFormulaVersion(String id, String status, String resultType) {
|
ElnFormulaVersionEntity version = new ElnFormulaVersionEntity();
|
version.setId(id);
|
version.setFTenantId("tenant-1");
|
version.setGongshiDingyiId("formula-def");
|
version.setBizStatus(status);
|
version.setJieguoLeixing(resultType);
|
version.setFVersion(1);
|
versions.put(id, version);
|
if (!store.slots.containsKey(id)) {
|
ElnFormulaParameterSlotEntity copied = new ElnFormulaParameterSlotEntity();
|
copied.setId("slot-" + id);
|
copied.setFTenantId("tenant-1");
|
copied.setGongshiBanbenId(id);
|
copied.setCanshuBianma("amount");
|
copied.setShujuLeixing("DECIMAL");
|
store.slots.put(id, Collections.singletonList(copied));
|
}
|
}
|
|
private void addField(String bindingId, String versionId, String type, String bindingConfig) {
|
ElnFieldVersionEntity version = new ElnFieldVersionEntity();
|
version.setId(versionId);
|
version.setFTenantId("tenant-1");
|
version.setZiduanDingyiId("field-" + bindingId);
|
version.setShujuLeixing(type);
|
version.setPiciMoshi("RECORD_PRIVATE");
|
version.setBangdingPeizhi(bindingConfig);
|
fieldVersions.put(versionId, version);
|
addFieldBinding(bindingId, versionId, "PRIMARY", null);
|
}
|
|
private void addRepeatField(String bindingId, String versionId, String groupCode) {
|
addField(bindingId, versionId, "DECIMAL", manualBinding());
|
ElnTemplateBindingEntity binding = store.fieldBindings.get(bindingId);
|
binding.setKongjianJuese("REPEAT");
|
binding.setRepeatGroupCode(groupCode);
|
binding.setRepeatPrototypeRowId("prototype");
|
binding.setKongjianBiaoqian("eln.repeat." + groupCode
|
+ ".prototype." + bindingId);
|
}
|
|
private void addFieldBinding(String id, String versionId, String role, String primary) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(id);
|
binding.setFTenantId("tenant-1");
|
binding.setMubanId("template-1");
|
binding.setZiduanBanbenId(versionId);
|
ElnFieldVersionEntity version = fieldVersions.get(versionId);
|
binding.setZiduanDingyiId(version == null ? null : version.getZiduanDingyiId());
|
binding.setKongjianBiaoqian("eln.field." + id);
|
binding.setKongjianJuese(role);
|
binding.setYinyongShiliId(primary);
|
binding.setBangdingZhuangtai("ACTIVE");
|
store.fieldBindings.put(id, binding);
|
}
|
|
private ElnFormulaForms.TemplateBinding request(String versionId, String operation,
|
Map<String, String> parameters, String result) {
|
ElnFormulaForms.TemplateBinding form = new ElnFormulaForms.TemplateBinding();
|
form.setFormulaVersionId(versionId);
|
form.setParameterBindings(parameters);
|
form.setResultBindingId(result);
|
form.setOperationCode(operation);
|
form.setJieguoSuoKuaizhao(Arrays.asList(control("result", ""),
|
control("result-ref", "contentLocked")));
|
return form;
|
}
|
|
private ElnFormulaForms.ControlSnapshot control(String bindingId, String lock) {
|
ElnTemplateBindingEntity binding = store.fieldBindings.get(bindingId);
|
ElnFormulaForms.ControlSnapshot result = new ElnFormulaForms.ControlSnapshot();
|
result.setBindingId(bindingId);
|
result.setControlTag(binding.getKongjianBiaoqian());
|
result.setControlRole(binding.getKongjianJuese());
|
result.setLock(lock);
|
return result;
|
}
|
|
private boolean hasAudit(String action) {
|
return audits.stream().anyMatch(item -> action.equals(item.getActionCode()));
|
}
|
|
private long auditCount(String action) {
|
return audits.stream().filter(item -> action.equals(item.getActionCode())).count();
|
}
|
|
private ElnFormulaVos.TemplateBinding activeMapping(String operation) {
|
ElnFormulaVos.TemplateBinding reserved = service.reserve(authorization(),
|
request("formula-current", operation, map("amount", "input"), "result"));
|
ElnFormulaForms.ActivateFormulaBinding activate =
|
new ElnFormulaForms.ActivateFormulaBinding();
|
activate.setOperationCode(operation); activate.setExpectedVersion(1);
|
activate.setControls(Arrays.asList(control("result", "sdtContentLocked"),
|
control("result-ref", "sdtContentLocked")));
|
service.activate(authorization(), reserved.getId(), activate);
|
reserved.setStatus("ACTIVE"); reserved.setVersion(2);
|
return reserved;
|
}
|
|
private ElnTemplateFormulaBindingEntity formulaBinding(String id, String template, String status) {
|
ElnTemplateFormulaBindingEntity item = new ElnTemplateFormulaBindingEntity();
|
item.setId(id);
|
item.setFTenantId("tenant-1");
|
item.setMubanId(template);
|
item.setGongshiDingyiId("formula-def");
|
item.setGongshiBanbenId("formula-current");
|
item.setCanshuYingshe(map("amount", "input"));
|
item.setJieguoBangdingId("result");
|
item.setBangdingZhuangtai(status);
|
item.setCaozuoBianma("foreign-op");
|
item.setJieguoSuoKuaizhao(Arrays.asList(control("result", ""),
|
control("result-ref", "contentLocked")));
|
item.setFVersion(1);
|
return item;
|
}
|
|
private TemplateAnnotationAuthorization authorization() {
|
try {
|
Constructor<TemplateAnnotationAuthorization> constructor =
|
TemplateAnnotationAuthorization.class.getDeclaredConstructor(
|
String.class, String.class, String.class, String.class);
|
constructor.setAccessible(true);
|
return constructor.newInstance("tenant-1", "user-1", "template-1", "file-1");
|
} catch (Exception ex) {
|
throw new AssertionError(ex);
|
}
|
}
|
}
|
|
private static String manualBinding() {
|
Map<String, Object> read = new LinkedHashMap<>();
|
read.put("source", "NONE");
|
Map<String, Object> write = new LinkedHashMap<>();
|
write.put("target", "ELN_RECORD");
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("read", read);
|
result.put("write", write);
|
return JsonUtil.getObjectToString(result);
|
}
|
|
private static class MemoryStore implements ElnTemplateFormulaBindingServiceImpl.Store {
|
protected ElnTemplateVersionEntity template;
|
private ElnFormulaDefEntity definition;
|
private final Map<String, ElnFormulaVersionEntity> versions = new LinkedHashMap<>();
|
private final Map<String, List<ElnFormulaParameterSlotEntity>> slots = new LinkedHashMap<>();
|
private final Map<String, ElnTemplateBindingEntity> fieldBindings = new LinkedHashMap<>();
|
private final Map<String, ElnFieldVersionEntity> fieldVersions = new LinkedHashMap<>();
|
protected final Map<String, ElnTemplateFormulaBindingEntity> bindings = new LinkedHashMap<>();
|
private ElnTemplateFormulaBindingEntity concurrentInsert;
|
private RuntimeException insertFailure;
|
private int findByOperationCalls;
|
|
@Override public ElnTemplateVersionEntity findTemplate(String tenant, String id) { return template; }
|
@Override public ElnTemplateVersionEntity lockTemplate(String tenant, String id) { return template; }
|
@Override public ElnFormulaDefEntity findFormulaDefinition(String tenant, String id) { return definition; }
|
@Override public ElnFormulaVersionEntity findFormulaVersion(String tenant, String id) { return versions.get(id); }
|
@Override public List<ElnFormulaParameterSlotEntity> listSlots(String tenant, String version) { return slots.getOrDefault(version, Collections.emptyList()); }
|
@Override public ElnTemplateBindingEntity findFieldBinding(String tenant, String id) { return fieldBindings.get(id); }
|
@Override public List<ElnTemplateBindingEntity> listFieldBindings(String tenant, String template) { List<ElnTemplateBindingEntity> result = new ArrayList<>(); for (ElnTemplateBindingEntity item : fieldBindings.values()) if (template.equals(item.getMubanId())) result.add(item); return result; }
|
@Override public ElnFieldVersionEntity findFieldVersion(String tenant, String id) { return fieldVersions.get(id); }
|
@Override public ElnTemplateFormulaBindingEntity findByOperation(String tenant, String template, String operation) { findByOperationCalls++; return bindings.values().stream().filter(item -> tenant.equals(item.getFTenantId()) && template.equals(item.getMubanId()) && operation.equals(item.getCaozuoBianma())).findFirst().orElse(null); }
|
@Override public ElnTemplateFormulaBindingEntity findBinding(String tenant, String id) { return bindings.get(id); }
|
@Override public List<ElnTemplateFormulaBindingEntity> listBindings(String tenant, String template) { List<ElnTemplateFormulaBindingEntity> result = new ArrayList<>(); for (ElnTemplateFormulaBindingEntity item : bindings.values()) if (template.equals(item.getMubanId())) result.add(item); return result; }
|
@Override public boolean insertIfAbsent(ElnTemplateFormulaBindingEntity binding) { if (insertFailure != null) throw insertFailure; if (concurrentInsert != null) { bindings.put(concurrentInsert.getId(), concurrentInsert); return false; } bindings.put(binding.getId(), binding); return true; }
|
@Override public boolean updateStatus(ElnTemplateFormulaBindingEntity binding, int expectedVersion, String status, String reason) { if (binding.getFVersion() != expectedVersion) return false; binding.setBangdingZhuangtai(status); binding.setZhuangtaiYuanyin(reason); binding.setFVersion(expectedVersion + 1); return true; }
|
@Override public void updateTopology(ElnTemplateFormulaBindingEntity binding, int order) { binding.setTuopuXuhao(order); }
|
|
private ElnTemplateFormulaBindingEntity binding(String id) { return bindings.get(id); }
|
}
|
|
private static final class ConcurrentGraphStore extends MemoryStore {
|
private final CyclicBarrier uncommittedGraphReads = new CyclicBarrier(2);
|
private final ReentrantLock templateLock = new ReentrantLock();
|
private final ThreadLocal<Boolean> templateLockHeld =
|
ThreadLocal.withInitial(() -> false);
|
private final ThreadLocal<ElnTemplateFormulaBindingEntity> staged = new ThreadLocal<>();
|
private final AtomicInteger lockCalls = new AtomicInteger();
|
|
@Override
|
public ElnTemplateVersionEntity lockTemplate(String tenantId, String templateId) {
|
lockCalls.incrementAndGet();
|
templateLock.lock();
|
templateLockHeld.set(true);
|
return template;
|
}
|
|
@Override
|
public synchronized ElnTemplateFormulaBindingEntity findBinding(
|
String tenantId, String id) {
|
return copyBinding(bindings.get(id));
|
}
|
|
@Override
|
public synchronized ElnTemplateFormulaBindingEntity findByOperation(
|
String tenantId, String templateId, String operationCode) {
|
for (ElnTemplateFormulaBindingEntity binding : bindings.values()) {
|
if (tenantId.equals(binding.getFTenantId())
|
&& templateId.equals(binding.getMubanId())
|
&& operationCode.equals(binding.getCaozuoBianma())) {
|
return copyBinding(binding);
|
}
|
}
|
return null;
|
}
|
|
@Override
|
public boolean updateStatus(ElnTemplateFormulaBindingEntity binding,
|
int expectedVersion, String status, String reason) {
|
if (binding.getFVersion() != expectedVersion) return false;
|
binding.setBangdingZhuangtai(status);
|
binding.setZhuangtaiYuanyin(reason);
|
binding.setFVersion(expectedVersion + 1);
|
staged.set(copyBinding(binding));
|
return true;
|
}
|
|
@Override
|
public List<ElnTemplateFormulaBindingEntity> listBindings(
|
String tenantId, String templateId) {
|
ElnTemplateFormulaBindingEntity local = staged.get();
|
if (local != null && !templateLockHeld.get()) {
|
try {
|
uncommittedGraphReads.await(5, TimeUnit.SECONDS);
|
} catch (Exception ex) {
|
throw new AssertionError("并发事务未同时进入图校验", ex);
|
}
|
}
|
Map<String, ElnTemplateFormulaBindingEntity> visible = new LinkedHashMap<>();
|
synchronized (this) {
|
for (ElnTemplateFormulaBindingEntity binding : bindings.values()) {
|
visible.put(binding.getId(), copyBinding(binding));
|
}
|
}
|
if (local != null) visible.put(local.getId(), copyBinding(local));
|
return new ArrayList<>(visible.values());
|
}
|
|
@Override
|
public void updateTopology(ElnTemplateFormulaBindingEntity binding, int order) {
|
ElnTemplateFormulaBindingEntity local = staged.get();
|
if (local != null && local.getId().equals(binding.getId())) {
|
local.setTuopuXuhao(order);
|
}
|
}
|
|
private void commitTransaction() {
|
ElnTemplateFormulaBindingEntity local = staged.get();
|
if (local != null) {
|
synchronized (this) {
|
bindings.put(local.getId(), copyBinding(local));
|
}
|
}
|
finishTransaction();
|
}
|
|
private void rollbackTransaction() {
|
finishTransaction();
|
}
|
|
private void finishTransaction() {
|
staged.remove();
|
if (templateLockHeld.get()) {
|
templateLockHeld.remove();
|
templateLock.unlock();
|
}
|
}
|
|
private synchronized int activeCount() {
|
int count = 0;
|
for (ElnTemplateFormulaBindingEntity binding : bindings.values()) {
|
if ("ACTIVE".equals(binding.getBangdingZhuangtai())) count++;
|
}
|
return count;
|
}
|
|
private synchronized List<ElnTemplateFormulaBindingEntity> committedBindings() {
|
List<ElnTemplateFormulaBindingEntity> result = new ArrayList<>();
|
for (ElnTemplateFormulaBindingEntity binding : bindings.values()) {
|
result.add(copyBinding(binding));
|
}
|
return result;
|
}
|
|
private static ElnTemplateFormulaBindingEntity copyBinding(
|
ElnTemplateFormulaBindingEntity binding) {
|
return binding == null ? null : JsonUtil.getJsonToBean(
|
JsonUtil.getObjectToString(binding), ElnTemplateFormulaBindingEntity.class);
|
}
|
}
|
|
private static final class LockCheckingStore extends MemoryStore {
|
private final AtomicInteger lockCalls = new AtomicInteger();
|
private boolean locked;
|
|
@Override
|
public ElnTemplateVersionEntity lockTemplate(String tenantId, String templateId) {
|
lockCalls.incrementAndGet();
|
locked = true;
|
return super.lockTemplate(tenantId, templateId);
|
}
|
|
@Override
|
public ElnTemplateVersionEntity findTemplate(String tenantId, String templateId) {
|
requireLocked();
|
return super.findTemplate(tenantId, templateId);
|
}
|
|
@Override
|
public List<ElnTemplateFormulaBindingEntity> listBindings(
|
String tenantId, String templateId) {
|
requireLocked();
|
return super.listBindings(tenantId, templateId);
|
}
|
|
@Override
|
public boolean updateStatus(ElnTemplateFormulaBindingEntity binding,
|
int expectedVersion, String status, String reason) {
|
requireLocked();
|
return super.updateStatus(binding, expectedVersion, status, reason);
|
}
|
|
@Override
|
public void updateTopology(ElnTemplateFormulaBindingEntity binding, int order) {
|
requireLocked();
|
super.updateTopology(binding, order);
|
}
|
|
private void requireLocked() {
|
if (!locked) throw new AssertionError("模板图读写必须位于数据库锁内");
|
}
|
}
|
|
private static final class CopyStore implements FormulaTemplateVersionCopyParticipant.Store {
|
private ElnTemplateVersionEntity target;
|
private final ElnFormulaVersionEntity formulaVersion = new ElnFormulaVersionEntity();
|
private final List<ElnTemplateFormulaBindingEntity> source = new ArrayList<>();
|
private final List<ElnTemplateFormulaBindingEntity> inserted = new ArrayList<>();
|
private ElnTemplateFormulaBindingEntity concurrentInsert;
|
private RuntimeException insertFailure;
|
private int findByOperationCalls;
|
private CopyStore() {
|
formulaVersion.setId("historical-published");
|
formulaVersion.setGongshiDingyiId("formula-def");
|
formulaVersion.setBizStatus("PUBLISHED");
|
}
|
private Map<String, String> prepareCopy() {
|
target = new ElnTemplateVersionEntity();
|
target.setId("target"); target.setFTenantId("tenant-1");
|
target.setBizStatus("DRAFT");
|
target.setShengbanCaozuoBianma("upgrade-1");
|
ElnTemplateFormulaBindingEntity item = new ElnTemplateFormulaBindingEntity();
|
item.setId("source-formula"); item.setFTenantId("tenant-1");
|
item.setMubanId("source"); item.setGongshiDingyiId("formula-def");
|
item.setGongshiBanbenId("historical-published");
|
item.setCanshuYingshe(map("amount", "old-input"));
|
item.setJieguoBangdingId("old-result"); item.setBangdingZhuangtai("ACTIVE");
|
source.add(item);
|
Map<String, String> fieldMap = new LinkedHashMap<>();
|
fieldMap.put("old-input", "new-input"); fieldMap.put("old-result", "new-result");
|
return fieldMap;
|
}
|
private ElnTemplateFormulaBindingEntity concurrentWinner(Map<String, String> fieldMap) {
|
ElnTemplateFormulaBindingEntity item = new ElnTemplateFormulaBindingEntity();
|
item.setId("concurrent-copy"); item.setFTenantId("tenant-1");
|
item.setMubanId("target"); item.setGongshiDingyiId("formula-def");
|
item.setGongshiBanbenId("historical-published");
|
item.setCanshuYingshe(map("amount", fieldMap.get("old-input")));
|
item.setJieguoBangdingId(fieldMap.get("old-result"));
|
item.setBangdingZhuangtai("PENDING");
|
item.setCaozuoBianma("upgrade-1:source-formula");
|
item.setLaiyuanGongshiBangdingId("source-formula");
|
item.setDaoqiShijian(null); item.setFVersion(1); item.setFDeleteMark(0);
|
return item;
|
}
|
@Override public ElnTemplateVersionEntity findTemplate(String tenantId, String templateId) {
|
return target;
|
}
|
@Override public List<ElnTemplateFormulaBindingEntity> listActive(
|
String tenantId, String sourceTemplateId) { return source; }
|
@Override public ElnFormulaVersionEntity findFormulaVersion(
|
String tenantId, String versionId) { return formulaVersion; }
|
@Override public ElnTemplateFormulaBindingEntity findByOperation(
|
String tenantId, String targetTemplateId, String operationCode) {
|
findByOperationCalls++;
|
return inserted.stream().filter(item -> tenantId.equals(item.getFTenantId())
|
&& targetTemplateId.equals(item.getMubanId())
|
&& operationCode.equals(item.getCaozuoBianma()))
|
.findFirst().orElse(null);
|
}
|
@Override public boolean insertIfAbsent(ElnTemplateFormulaBindingEntity binding) { if (insertFailure != null) throw insertFailure; inserted.add(concurrentInsert == null ? binding : concurrentInsert); return concurrentInsert == null; }
|
}
|
}
|