CasMerge { public void reMergeCas(DiffResult aDiff, SourceDocument aTargetDocument, String aTargetUsername, CAS aTargetCas, Map<String, CAS> aCases) throws AnnotationException, UIMAException { silenceEvents = true; int updated = 0; int created = 0; Set<LogMessage> messages = new LinkedHashSet<>(); clearAnnotations(aTargetCas); if (aCases.isEmpty()) { return; } Map<String, AnnotationLayer> type2layer = aDiff.getPositions().stream() .map(Position::getType) .distinct() .map(type -> schemaService.findLayer(aTargetDocument.getProject(), type)) .collect(toMap(AnnotationLayer::getName, identity())); List<String> layerNames = new ArrayList<>(type2layer.keySet()); if (layerNames.contains(Token.class.getName())) { layerNames.remove(Token.class.getName()); layerNames.add(0, Token.class.getName()); } if (layerNames.contains(Sentence.class.getName())) { layerNames.remove(Sentence.class.getName()); layerNames.add(0, Sentence.class.getName()); } for (String layerName : layerNames) { List<SpanPosition> positions = aDiff.getPositions().stream() .filter(pos -> layerName.equals(pos.getType())) .filter(pos -> pos instanceof SpanPosition) .map(pos -> (SpanPosition) pos) .filter(pos -> pos.getFeature() == null) .collect(Collectors.toList()); if (positions.isEmpty()) { continue; } LOG.debug("Processing {} span positions on layer {}", positions.size(), layerName); for (SpanPosition position : positions) { LOG.trace(" | processing {}", position); ConfigurationSet cfgs = aDiff.getConfigurtionSet(position); if (!shouldMerge(aDiff, cfgs)) { continue; } try { Map<String, List<CAS>> casMap = new LinkedHashMap<>(); aCases.forEach((k, v) -> casMap.put(k, asList(v))); AnnotationFS sourceFS = (AnnotationFS) cfgs.getConfigurations().get(0) .getRepresentative(casMap); CasMergeOperationResult result = mergeSpanAnnotation(aTargetDocument, aTargetUsername, type2layer.get(position.getType()), aTargetCas, sourceFS, false); LOG.trace(" `-> merged annotation with agreement"); switch (result.getState()) { case CREATED: created++; break; case UPDATED: updated++; break; } } catch (AnnotationException e) { LOG.trace(" `-> not merged annotation: {}", e.getMessage()); messages.add(LogMessage.error(this, "%s", e.getMessage())); } } } for (String layerName : layerNames) { List<SpanPosition> positions = aDiff.getPositions().stream() .filter(pos -> layerName.equals(pos.getType())) .filter(pos -> pos instanceof SpanPosition) .map(pos -> (SpanPosition) pos) .filter(pos -> pos.getFeature() != null) .collect(Collectors.toList()); if (positions.isEmpty()) { continue; } LOG.debug("Processing {} slot positions on layer [{}]", positions.size(), layerName); for (SpanPosition position : positions) { LOG.trace(" | processing {}", position); ConfigurationSet cfgs = aDiff.getConfigurtionSet(position); if (!shouldMerge(aDiff, cfgs)) { continue; } try { Map<String, List<CAS>> casMap = new LinkedHashMap<>(); aCases.forEach((k, v) -> casMap.put(k, asList(v))); AnnotationFS sourceFS = (AnnotationFS) cfgs.getConfigurations().get(0) .getRepresentative(casMap); AID sourceFsAid = cfgs.getConfigurations().get(0) .getRepresentativeAID(); mergeSlotFeature(aTargetDocument, aTargetUsername, type2layer.get(position.getType()), aTargetCas, sourceFS, sourceFsAid.feature, sourceFsAid.index); LOG.trace(" `-> merged annotation with agreement"); } catch (AnnotationException e) { LOG.trace(" `-> not merged annotation: {}", e.getMessage()); messages.add(LogMessage.error(this, "%s", e.getMessage())); } } } for (String layerName : layerNames) { List<RelationPosition> positions = aDiff.getPositions().stream() .filter(pos -> layerName.equals(pos.getType())) .filter(pos -> pos instanceof RelationPosition) .map(pos -> (RelationPosition) pos) .collect(Collectors.toList()); if (positions.isEmpty()) { continue; } LOG.debug("Processing {} relation positions on layer [{}]", positions.size(), layerName); for (RelationPosition position : positions) { LOG.trace(" | processing {}", position); ConfigurationSet cfgs = aDiff.getConfigurtionSet(position); if (!shouldMerge(aDiff, cfgs)) { continue; } try { Map<String, List<CAS>> casMap = new LinkedHashMap<>(); aCases.forEach((k, v) -> casMap.put(k, asList(v))); AnnotationFS sourceFS = (AnnotationFS) cfgs.getConfigurations().get(0) .getRepresentative(casMap); CasMergeOperationResult result = mergeRelationAnnotation(aTargetDocument, aTargetUsername, type2layer.get(position.getType()), aTargetCas, sourceFS, false); LOG.trace(" `-> merged annotation with agreement"); switch (result.getState()) { case CREATED: created++; break; case UPDATED: updated++; break; } } catch (AnnotationException e) { LOG.trace(" `-> not merged annotation: {}", e.getMessage()); messages.add(LogMessage.error(this, "%s", e.getMessage())); } } } if (eventPublisher != null) { eventPublisher.publishEvent( new BulkAnnotationEvent(this, aTargetDocument, aTargetUsername, null)); } } CasMerge(AnnotationSchemaService aSchemaService);  CasMerge(AnnotationSchemaService aSchemaService,
            ApplicationEventPublisher aEventPublisher); void setSilenceEvents(boolean aSilenceEvents); boolean isSilenceEvents(); void setMergeIncompleteAnnotations(boolean aMergeIncompleteAnnotations); boolean isMergeIncompleteAnnotations(); void reMergeCas(DiffResult aDiff, SourceDocument aTargetDocument, String aTargetUsername,
            CAS aTargetCas, Map<String, CAS> aCases); CasMergeOperationResult mergeSpanAnnotation(SourceDocument aDocument, String aUsername,
            AnnotationLayer aAnnotationLayer, CAS aTargetCas, AnnotationFS aSourceFs,
            boolean aAllowStacking); CasMergeOperationResult mergeRelationAnnotation(SourceDocument aDocument,
            String aUsername, AnnotationLayer aAnnotationLayer, CAS aTargetCas,
            AnnotationFS aSourceFs, boolean aAllowStacking); CasMergeOperationResult mergeSlotFeature(SourceDocument aDocument, String aUsername,
            AnnotationLayer aAnnotationLayer, CAS aTargetCas,
            AnnotationFS aSourceFs, String aSourceFeature, int aSourceSlotIndex);  }
@Test public void thatIncompleteAnnotationIsNotMerged() throws Exception { JCas user1 = JCasFactory.createText("word"); token(user1, 0, 4, "X"); JCas user2 = JCasFactory.createText("word"); token(user2, 0, 4, null); Map<String, List<CAS>> casByUser = new LinkedHashMap<>(); casByUser.put("user1", asList(user1.getCas())); casByUser.put("user2", asList(user2.getCas())); JCas curatorCas = createText(casByUser.values().stream() .flatMap(Collection::stream).findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser) .toResult(); sut.reMergeCas(result, document, null, curatorCas.getCas(), getSingleCasByUser(casByUser)); assertThat(result.getDifferingConfigurationSets()).isEmpty(); assertThat(result.getIncompleteConfigurationSets().values()) .extracting(set -> set.getPosition()) .usingFieldByFieldElementComparator() .containsExactly(new SpanPosition(null, null, 0, POS.class.getName(), 0, 4, "word", null, null, -1, -1, null, null)); assertThat(select(curatorCas, POS.class)).isEmpty(); }
@Test public void multiLinkWithRoleNoDifferenceTest() throws Exception { JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); makeLinkHostFS(jcasA, 10, 10, makeLinkFS(jcasA, "slot1", 10, 10)); JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 0, 0)); makeLinkHostFS(jcasB, 10, 10, makeLinkFS(jcasB, "slot1", 10, 10)); Map<String, List<CAS>> casByUser = new LinkedHashMap<>(); casByUser.put("user1", asList(jcasA.getCas())); casByUser.put("user2", asList(jcasB.getCas())); JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) .findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); sut.reMergeCas(result, document, null, curatorCas.getCas(), getSingleCasByUser(casByUser)); casByUser = new HashMap<>(); casByUser.put("actual", asList(jcasA.getCas())); casByUser.put("merge", asList(curatorCas.getCas())); result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); assertEquals(0, result.getDifferingConfigurationSets().size()); assertEquals(0, result.getIncompleteConfigurationSets().size()); }
@Test public void multiLinkWithRoleLabelDifferenceTest() throws Exception { JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot2", 0, 0)); Map<String, List<CAS>> casByUser = new LinkedHashMap<>(); casByUser.put("user1", asList(jcasA.getCas())); casByUser.put("user2", asList(jcasB.getCas())); JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) .findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); sut.reMergeCas(result, document, null, curatorCas.getCas(), getSingleCasByUser(casByUser)); Type hostType = curatorCas.getCas().getTypeSystem().getType(HOST_TYPE); FeatureSupport slotSupport = featureSupportRegistry.getFeatureSupport(slotFeature); assertThat(select(curatorCas.getCas(), hostType)) .hasSize(1); assertThat(select(curatorCas.getCas(), hostType).stream() .map(host -> (List) slotSupport.getFeatureValue(slotFeature, host))) .allMatch(Collection::isEmpty); }
@Test public void multiLinkWithRoleTargetDifferenceTest() throws Exception { JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 10, 10)); Map<String, List<CAS>> casByUser = new LinkedHashMap<>(); casByUser.put("user1", asList(jcasA.getCas())); casByUser.put("user2", asList(jcasB.getCas())); JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) .findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); sut.reMergeCas(result, document, null, curatorCas.getCas(), getSingleCasByUser(casByUser)); Type hostType = curatorCas.getCas().getTypeSystem().getType(HOST_TYPE); FeatureSupport slotSupport = featureSupportRegistry.getFeatureSupport(slotFeature); assertThat(select(curatorCas.getCas(), hostType)) .hasSize(1); assertThat(select(curatorCas.getCas(), hostType).stream() .map(host -> (List) slotSupport.getFeatureValue(slotFeature, host))) .allMatch(Collection::isEmpty); }
@Test public void multiLinkMultiHostTest() throws Exception { JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 0, 0)); Map<String, List<CAS>> casByUser = new LinkedHashMap<>(); casByUser.put("user1", asList(jcasA.getCas())); casByUser.put("user2", asList(jcasB.getCas())); CAS curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")).getCas(); curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) .findFirst().get().getDocumentText()); SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); DiffResult result = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser).toResult(); sut.reMergeCas(result, document, null, curatorCas, getSingleCasByUser(casByUser)); assertThat(select(curatorCas, getType(curatorCas, HOST_TYPE))) .isEmpty(); }
@Test public void multiLinkMultiSpanRoleDiffTest() throws Exception { JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); Type type = jcasA.getTypeSystem().getType(HOST_TYPE); Feature feature = type.getFeatureByBaseName("f1"); makeLinkHostMultiSPanFeatureFS(jcasA, 0, 0, feature, "A", makeLinkFS(jcasA, "slot1", 0, 0)); JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostMultiSPanFeatureFS(jcasB, 0, 0, feature, "A", makeLinkFS(jcasB, "slot2", 0, 0)); Map<String, List<CAS>> casByUser = new LinkedHashMap<>(); casByUser.put("user1", asList(jcasA.getCas())); casByUser.put("user2", asList(jcasB.getCas())); JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) .findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); sut.reMergeCas(result, document, null, curatorCas.getCas(), getSingleCasByUser(casByUser)); Type hostType = curatorCas.getTypeSystem().getType(HOST_TYPE); assertThat(select(curatorCas.getCas(), hostType)) .hasSize(1); }