From 84a2efa94284de0b75c51a3a2ad561c9e6f2bdba Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Thu, 7 Mar 2024 10:06:38 +0100 Subject: [PATCH] #4603 - Bulk actions in search sidebar should not be greyed-out when in sidebar curation mode - Allow access to be bulk-action buttons as long as the current CAS is editable (probably we should even always allow access because the bulk action may not affect the current CAS only but others --- .../events/BulkAnnotationEvent.java | 12 +- .../annotation/layer/span/SpanAdapter.java | 6 +- .../webanno/ui/annotation/AnnotationPage.java | 2 +- .../sidebar/SearchAnnotationSidebar.java | 294 +++++++++--------- 4 files changed, 151 insertions(+), 163 deletions(-) diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BulkAnnotationEvent.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BulkAnnotationEvent.java index 3b64b6f7e66..95762dc2673 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BulkAnnotationEvent.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BulkAnnotationEvent.java @@ -34,21 +34,21 @@ public class BulkAnnotationEvent { private static final long serialVersionUID = -1187536069360130349L; - public BulkAnnotationEvent(Object aSource, Project aProject, String aDocumentOwner, + public BulkAnnotationEvent(Object aSource, Project aProject, String aDataOwner, AnnotationLayer aLayer) { - super(aSource, aProject, aDocumentOwner, aLayer); + super(aSource, aProject, aDataOwner, aLayer); } - public BulkAnnotationEvent(Object aSource, SourceDocument aDocument, String aDocumentOwner, + public BulkAnnotationEvent(Object aSource, SourceDocument aDocument, String aDataOwner, AnnotationLayer aLayer) { - super(aSource, aDocument, aDocumentOwner, aLayer); + super(aSource, aDocument, aDataOwner, aLayer); } - public BulkAnnotationEvent(Object aSource, SourceDocument aDocument, String aDocumentOwner) + public BulkAnnotationEvent(Object aSource, SourceDocument aDocument, String aDataOwner) { - super(aSource, aDocument, aDocumentOwner); + super(aSource, aDocument, aDataOwner); } @Override diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java index f086f271719..2ed2e4c3e23 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java @@ -83,7 +83,7 @@ public SpanAdapter(LayerSupportRegistry aLayerSupportRegistry, * * @param aDocument * the document to which the CAS belongs - * @param aDocumentOwner + * @param aDataOwner * the user to which the CAS belongs * @param aCas * the CAS. @@ -95,12 +95,12 @@ public SpanAdapter(LayerSupportRegistry aLayerSupportRegistry, * @throws AnnotationException * if the annotation cannot be created/updated. */ - public AnnotationFS add(SourceDocument aDocument, String aDocumentOwner, CAS aCas, int aBegin, + public AnnotationFS add(SourceDocument aDocument, String aDataOwner, CAS aCas, int aBegin, int aEnd) throws AnnotationException { return handle( - new CreateSpanAnnotationRequest(aDocument, aDocumentOwner, aCas, aBegin, aEnd)); + new CreateSpanAnnotationRequest(aDocument, aDataOwner, aCas, aBegin, aEnd)); } public AnnotationFS handle(CreateSpanAnnotationRequest aRequest) throws AnnotationException diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java index 601ba3d925e..f83d4b026b3 100755 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java @@ -403,7 +403,7 @@ public CAS getEditorCas() throws IOException public void writeEditorCas(CAS aCas) throws IOException, AnnotationException { ensureIsEditable(); - AnnotatorState state = getModelObject(); + var state = getModelObject(); documentService.writeAnnotationCas(aCas, state.getDocument(), state.getUser(), true); bumpAnnotationCasTimestamp(state); diff --git a/inception/inception-ui-search/src/main/java/de/tudarmstadt/ukp/inception/app/ui/search/sidebar/SearchAnnotationSidebar.java b/inception/inception-ui-search/src/main/java/de/tudarmstadt/ukp/inception/app/ui/search/sidebar/SearchAnnotationSidebar.java index 2dd3c21de06..ea892e72853 100644 --- a/inception/inception-ui-search/src/main/java/de/tudarmstadt/ukp/inception/app/ui/search/sidebar/SearchAnnotationSidebar.java +++ b/inception/inception-ui-search/src/main/java/de/tudarmstadt/ukp/inception/app/ui/search/sidebar/SearchAnnotationSidebar.java @@ -31,16 +31,13 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import org.apache.uima.cas.CAS; -import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; import org.apache.wicket.AttributeModifier; @@ -80,14 +77,11 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.navigation.ajax.BootstrapAjaxPagingNavigator; import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType; import de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasProvider; -import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.LinkMode; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage; import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.sidebar.AnnotationSidebar_ImplBase; import de.tudarmstadt.ukp.inception.annotation.events.BulkAnnotationEvent; @@ -99,7 +93,6 @@ import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; -import de.tudarmstadt.ukp.inception.rendering.editorstate.FeatureState; import de.tudarmstadt.ukp.inception.rendering.pipeline.RenderAnnotationsEvent; import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; import de.tudarmstadt.ukp.inception.rendering.vmodel.VRange; @@ -111,7 +104,6 @@ import de.tudarmstadt.ukp.inception.search.SearchService; import de.tudarmstadt.ukp.inception.search.config.SearchProperties; import de.tudarmstadt.ukp.inception.search.event.SearchQueryEvent; -import de.tudarmstadt.ukp.inception.search.model.Progress; import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxButton; import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxFormComponentUpdatingBehavior; import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink; @@ -149,7 +141,7 @@ public class SearchAnnotationSidebar private static final long serialVersionUID = -3358207848681467993L; - private static final Logger LOG = LoggerFactory.getLogger(SearchAnnotationSidebar.class); + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @SpringBean DocumentService documentService; private @SpringBean AnnotationSchemaService annotationService; @@ -159,8 +151,6 @@ public class SearchAnnotationSidebar private @SpringBean SearchProperties searchProperties; private @SpringBean WorkloadManagementService workloadService; - private User currentUser; - private final WebMarkupContainer mainContainer; private final WebMarkupContainer resultsGroupContainer; private final WebMarkupContainer resultsTable; @@ -199,7 +189,6 @@ public SearchAnnotationSidebar(String aId, IModel aModel, { super(aId, aModel, aActionHandler, aCasProvider, aAnnotationPage); - currentUser = userRepository.getCurrentUser(); resultsProvider = new SearchResultsProviderWrapper( new SearchResultsProvider(searchService, groupedResults), searchOptions.bind("isLowLevelPaging")); @@ -245,7 +234,7 @@ public SearchAnnotationSidebar(String aId, IModel aModel, @Override protected void populateItem(Item item) { - ResultsGroup result = item.getModelObject(); + var result = item.getModelObject(); item.add(new Label(MID_GROUP_TITLE, LoadableDetachableModel.of(() -> groupSizeLabelValue(result)))); item.add(createGroupLevelSelectionCheckBox(MID_SELECT_ALL_IN_GROUP, @@ -320,8 +309,8 @@ private Label createNumberOfResults(String aId) var label = new Label(aId); label.setOutputMarkupId(true); label.setDefaultModel(LoadableDetachableModel.of(() -> { - long first = searchResultGroups.getFirstItemOffset(); - long total = searchResultGroups.getItemCount(); + var first = searchResultGroups.getFirstItemOffset(); + var total = searchResultGroups.getItemCount(); return format("%d-%d / %d", first + 1, min(first + searchResultGroups.getItemsPerPage(), total), total); })); @@ -351,18 +340,19 @@ protected void onAjaxEvent(AjaxRequestTarget aTarget) private Form createSearchForm(String aId) { - Form searchForm = new Form<>(aId); + var searchForm = new Form(aId); searchForm.add(new TextArea<>("queryInput", targetQuery)); - LambdaAjaxButton searchButton = new LambdaAjaxButton<>("search", - this::actionSearch); + + var searchButton = new LambdaAjaxButton<>("search", this::actionSearch); searchForm.add(searchButton); searchForm.setDefaultButton(searchButton); + return searchForm; } private Form createSearchOptionsForm(String aId) { - Form searchOptionsForm = new Form<>(aId, searchOptions); + var searchOptionsForm = new Form<>(aId, searchOptions); searchOptionsForm.add(createLayerDropDownChoice("groupingLayer", annotationService.listAnnotationLayer(getModelObject().getProject()))); @@ -384,8 +374,7 @@ protected void onConfigure() { super.onConfigure(); - setChangeAnnotationsElementsEnabled( - !getModelObject().isUserViewingOthersWork(userRepository.getCurrentUsername())); + setChangeAnnotationsElementsEnabled(getAnnotationPage().isEditable()); } @Override @@ -393,7 +382,6 @@ public void renderHead(IHeaderResponse aResponse) { super.renderHead(aResponse); - // CSS aResponse.render(CssHeaderItem.forReference(SearchAnnotationSidebarCssReference.get())); } @@ -409,7 +397,7 @@ private void setChangeAnnotationsElementsEnabled(boolean aEnabled) private String groupSizeLabelValue(ResultsGroup aResultsGroup) { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.append(aResultsGroup.getGroupKey() + " (" + aResultsGroup.getResults().size()); if (!resultsProvider.applyLowLevelPaging()) { sb.append("/" + resultsProvider.groupSize(aResultsGroup.getGroupKey())); @@ -420,8 +408,7 @@ private String groupSizeLabelValue(ResultsGroup aResultsGroup) private DropDownChoice createResultsPerPageSelection(String aId) { - List choices = Arrays.stream(searchProperties.getPageSizes()).boxed() - .collect(Collectors.toList()); + var choices = Arrays.stream(searchProperties.getPageSizes()).boxed().toList(); var dropdown = new DropDownChoice<>(aId, choices); dropdown.add(new LambdaAjaxFormComponentUpdatingBehavior()); @@ -431,7 +418,7 @@ private DropDownChoice createResultsPerPageSelection(String aId) private DropDownChoice createLayerDropDownChoice(String aId, List aChoices) { - DropDownChoice layerChoice = new DropDownChoice<>(aId, aChoices, + var layerChoice = new DropDownChoice(aId, aChoices, new ChoiceRenderer<>("uiName")); layerChoice.add(new AjaxFormComponentUpdatingBehavior("change") @@ -454,7 +441,7 @@ protected void onUpdate(AjaxRequestTarget aTarget) private CheckBox createLowLevelPagingCheckBox() { - CheckBox checkbox = new CheckBox("lowLevelPaging"); + var checkbox = new CheckBox("lowLevelPaging"); checkbox.setOutputMarkupId(true); checkbox.add(enabledWhen(() -> searchOptions.getObject().getGroupingLayer() == null && searchOptions.getObject().getGroupingFeature() == null)); @@ -466,7 +453,7 @@ private CheckBox createLowLevelPagingCheckBox() private AjaxCheckBox createGroupLevelSelectionCheckBox(String aId, String aGroupKey) { - AjaxCheckBox selectAllCheckBox = new AjaxCheckBox(aId, Model.of(true)) + return new AjaxCheckBox(aId, Model.of(true)) { private static final long serialVersionUID = 2431702654443882657L; @@ -503,7 +490,6 @@ protected void onConfigure() .allMatch(SearchResult::isSelectedForAnnotation)); } }; - return selectAllCheckBox; } private void actionSearch(AjaxRequestTarget aTarget, Form aForm) @@ -524,8 +510,8 @@ private IResourceStream exportSearchResults() @Override public InputStream getInputStream() throws ResourceStreamNotFoundException { - SearchResultsExporter exporter = new SearchResultsExporter(); try { + var exporter = new SearchResultsExporter(); return exporter.generateCsv(resultsProvider.getAllResults()); } catch (Exception e) { @@ -558,12 +544,12 @@ private void executeSearchResultsGroupedQuery(AjaxRequestTarget aTarget) return; } - AnnotatorState state = getModelObject(); - Project project = state.getProject(); + var state = getModelObject(); + var project = state.getProject(); - Optional maybeProgress = searchService.getIndexProgress(project); + var maybeProgress = searchService.getIndexProgress(project); if (maybeProgress.isPresent()) { - Progress p = maybeProgress.get(); + var p = maybeProgress.get(); info("Indexing in progress... cannot perform query at this time. " + p.percent() + "% (" + p.getDone() + "/" + p.getTotal() + ")"); aTarget.addChildren(getPage(), IFeedback.class); @@ -579,7 +565,7 @@ private void executeSearchResultsGroupedQuery(AjaxRequestTarget aTarget) } try { - SourceDocument limitToDocument = state.getDocument(); + var limitToDocument = state.getDocument(); if (workloadService.getWorkloadManagerExtension(project).isDocumentRandomAccessAllowed( project) && !searchOptions.getObject().isLimitedToCurrentDocument()) { limitToDocument = null; @@ -587,7 +573,7 @@ private void executeSearchResultsGroupedQuery(AjaxRequestTarget aTarget) applicationEventPublisher.get().publishEvent(new SearchQueryEvent(this, project, state.getUser().getUsername(), targetQuery.getObject(), limitToDocument)); - SearchOptions opt = searchOptions.getObject(); + var opt = searchOptions.getObject(); resultsProvider.initializeQuery(getModelObject().getUser(), project, targetQuery.getObject(), limitToDocument, opt.getGroupingLayer(), opt.getGroupingFeature()); @@ -617,93 +603,93 @@ public void actionApplyToSelectedResults(AjaxRequestTarget aTarget, Operation aC aTarget.addChildren(getPage(), IFeedback.class); if (VID.NONE_ID.equals(getModelObject().getSelection().getAnnotation())) { error("No annotation selected. Please select an annotation first"); + getAnnotationPage().actionRefreshDocument(aTarget); + return; } - else { - AnnotationLayer layer = getModelObject().getSelectedAnnotationLayer(); - try { - SpanAdapter adapter = (SpanAdapter) annotationService.getAdapter(layer); - adapter.silenceEvents(); - - // Group the results by document such that we can process one CAS at a time - Map> resultsByDocument = groupedResults.getObject() - .allResultsGroups().stream() - // the grouping can be based on some other strategy than the document, so - // we re-group here - .flatMap(group -> group.getResults().stream()) - .collect(groupingBy(SearchResult::getDocumentId)); - - BulkOperationResult bulkResult = new BulkOperationResult(); - - AnnotatorState state = getModelObject(); - for (Entry> resultsGroup : resultsByDocument.entrySet()) { - long documentId = resultsGroup.getKey(); - SourceDocument sourceDoc = documentService - .getSourceDocument(state.getProject().getId(), documentId); - - AnnotationDocument annoDoc = documentService - .createOrGetAnnotationDocument(sourceDoc, currentUser); - - switch (annoDoc.getState()) { - case FINISHED: // fall-through - case IGNORE: - // Skip processing any documents which are finished or ignored - continue; - default: - // Do nothing - } - - // Holder for lazily-loaded CAS - Optional cas = Optional.empty(); - // Apply bulk operations to all hits from this document - for (SearchResult result : resultsGroup.getValue()) { - if (result.isReadOnly() || !result.isSelectedForAnnotation()) { - continue; - } + var dataOwner = getAnnotationPage().getModelObject().getUser(); + var layer = getModelObject().getSelectedAnnotationLayer(); + try { + var adapter = (SpanAdapter) annotationService.getAdapter(layer); + adapter.silenceEvents(); + + // Group the results by document such that we can process one CAS at a time + var resultsByDocument = groupedResults.getObject().allResultsGroups().stream() + // the grouping can be based on some other strategy than the document, so + // we re-group here + .flatMap(group -> group.getResults().stream()) + .collect(groupingBy(SearchResult::getDocumentId)); + + var bulkResult = new BulkOperationResult(); + + var state = getModelObject(); + for (var resultsGroup : resultsByDocument.entrySet()) { + var documentId = resultsGroup.getKey(); + var sourceDoc = documentService.getSourceDocument(state.getProject().getId(), + documentId); + + var annoDoc = documentService.createOrGetAnnotationDocument(sourceDoc, dataOwner); + + switch (annoDoc.getState()) { + case FINISHED: // fall-through + case IGNORE: + // Skip processing any documents which are finished or ignored + continue; + default: + // Do nothing + } - if (!cas.isPresent()) { - // Lazily load annotated document - cas = Optional.of(documentService.readAnnotationCas(sourceDoc, - currentUser.getUsername(), AUTO_CAS_UPGRADE)); - } + // Holder for lazily-loaded CAS + Optional cas = Optional.empty(); - aConsumer.apply(sourceDoc, cas.get(), adapter, result, bulkResult); + // Apply bulk operations to all hits from this document + for (var result : resultsGroup.getValue()) { + if (result.isReadOnly() || !result.isSelectedForAnnotation()) { + continue; } - // Persist annotated document - if (cas.isPresent()) { - writeJCasAndUpdateTimeStamp(sourceDoc, cas.get()); + if (!cas.isPresent()) { + // Lazily load annotated document + cas = Optional + .of(documentService.readAnnotationCas(annoDoc, AUTO_CAS_UPGRADE)); } - } - if (bulkResult.created > 0) { - success("Created annotations: " + bulkResult.created); - } - if (bulkResult.updated > 0) { - success("Updated annotations: " + bulkResult.updated); - } - if (bulkResult.deleted > 0) { - success("Deleted annotations: " + bulkResult.deleted); - } - if (bulkResult.conflict > 0) { - warn("Annotations skipped due to conflicts: " + bulkResult.conflict); + aConsumer.apply(sourceDoc, cas.get(), adapter, result, bulkResult); } - if (bulkResult.created == 0 && bulkResult.updated == 0 && bulkResult.deleted == 0) { - info("No changes"); + // Persist annotated document + if (cas.isPresent()) { + writeJCasAndUpdateTimeStamp(sourceDoc, cas.get()); } + } - applicationEventPublisher.get().publishEvent(new BulkAnnotationEvent(this, - getModelObject().getProject(), currentUser.getUsername(), layer)); + if (bulkResult.created > 0) { + success("Created annotations: " + bulkResult.created); + } + if (bulkResult.updated > 0) { + success("Updated annotations: " + bulkResult.updated); + } + if (bulkResult.deleted > 0) { + success("Deleted annotations: " + bulkResult.deleted); } - catch (ClassCastException e) { - error("Can only create SPAN annotations for search results."); - LOG.error("Can only create SPAN annotations for search results", e); + if (bulkResult.conflict > 0) { + warn("Annotations skipped due to conflicts: " + bulkResult.conflict); } - catch (Exception e) { - error("Unable to apply action to search results: " + e.getMessage()); - LOG.error("Unable to apply action to search results: ", e); + + if (bulkResult.created == 0 && bulkResult.updated == 0 && bulkResult.deleted == 0) { + info("No changes"); } + + applicationEventPublisher.get().publishEvent(new BulkAnnotationEvent(this, + getModelObject().getProject(), dataOwner.getUsername(), layer)); + } + catch (ClassCastException e) { + error("Can only create SPAN annotations for search results."); + LOG.error("Can only create SPAN annotations for search results", e); + } + catch (Exception e) { + error("Unable to apply action to search results: " + e.getMessage()); + LOG.error("Unable to apply action to search results: ", e); } getAnnotationPage().actionRefreshDocument(aTarget); @@ -713,14 +699,14 @@ private void createAnnotationAtSearchResult(SourceDocument aDocument, CAS aCas, SpanAdapter aAdapter, SearchResult aSearchResult, BulkOperationResult aBulkResult) throws AnnotationException { - AnnotatorState state = getModelObject(); - AnnotationLayer layer = aAdapter.getLayer(); + var state = getModelObject(); + var layer = aAdapter.getLayer(); - Type type = CasUtil.getAnnotationType(aCas, aAdapter.getAnnotationTypeName()); - AnnotationFS annoFS = selectAt(aCas, type, aSearchResult.getOffsetStart(), + var type = CasUtil.getAnnotationType(aCas, aAdapter.getAnnotationTypeName()); + var annoFS = selectAt(aCas, type, aSearchResult.getOffsetStart(), aSearchResult.getOffsetEnd()).stream().findFirst().orElse(null); - boolean overrideExisting = createOptions.getObject().isOverrideExistingAnnotations(); + var overrideExisting = createOptions.getObject().isOverrideExistingAnnotations(); // if there is already an annotation of the same type at the target location // and we don't want to override it and stacking is not enabled, do nothing. @@ -728,11 +714,11 @@ private void createAnnotationAtSearchResult(SourceDocument aDocument, CAS aCas, return; } - boolean match = false; + var match = false; // create a new annotation if not already there or if stacking is enabled and the // new annotation has different features than the existing one - for (AnnotationFS eannoFS : selectAt(aCas, type, aSearchResult.getOffsetStart(), + for (var eannoFS : selectAt(aCas, type, aSearchResult.getOffsetStart(), aSearchResult.getOffsetEnd())) { if (overrideExisting) { setFeatureValues(aDocument, aCas, aAdapter, state, eannoFS); @@ -745,7 +731,7 @@ else if (featureValuesMatchCurrentState(eannoFS)) { if (annoFS == null || (!match && !overrideExisting)) { try { - annoFS = aAdapter.add(aDocument, currentUser.getUsername(), aCas, + annoFS = aAdapter.add(aDocument, state.getUser().getUsername(), aCas, aSearchResult.getOffsetStart(), aSearchResult.getOffsetEnd()); aBulkResult.created++; } @@ -763,18 +749,19 @@ private void setFeatureValues(SourceDocument aDocument, CAS aCas, SpanAdapter aA AnnotatorState state, AnnotationFS annoFS) throws AnnotationException { - int addr = ICasUtil.getAddr(annoFS); - List featureStates = state.getFeatureStates(); - for (FeatureState featureState : featureStates) { - Object featureValue = featureState.value; - AnnotationFeature feature = featureState.feature; + var addr = ICasUtil.getAddr(annoFS); + for (var featureState : state.getFeatureStates()) { + var featureValue = featureState.value; + var feature = featureState.feature; + // Ignore slot features - cf. https://github.com/inception-project/inception/issues/2505 if (feature.getLinkMode() != LinkMode.NONE) { continue; } + if (featureValue != null) { - aAdapter.setFeatureValue(aDocument, currentUser.getUsername(), aCas, addr, feature, - featureValue); + aAdapter.setFeatureValue(aDocument, state.getUser().getUsername(), aCas, addr, + feature, featureValue); } } } @@ -782,13 +769,14 @@ private void setFeatureValues(SourceDocument aDocument, CAS aCas, SpanAdapter aA private void deleteAnnotationAtSearchResult(SourceDocument aDocument, CAS aCas, SpanAdapter aAdapter, SearchResult aSearchResult, BulkOperationResult aBulkResult) { - Type type = CasUtil.getAnnotationType(aCas, aAdapter.getAnnotationTypeName()); + var dataOwner = getAnnotationPage().getModelObject().getUser(); + var type = CasUtil.getAnnotationType(aCas, aAdapter.getAnnotationTypeName()); - for (AnnotationFS annoFS : selectAt(aCas, type, aSearchResult.getOffsetStart(), + for (var annoFS : selectAt(aCas, type, aSearchResult.getOffsetStart(), aSearchResult.getOffsetEnd())) { if ((annoFS != null && featureValuesMatchCurrentState(annoFS)) || !deleteOptions.getObject().isDeleteOnlyMatchingFeatureValues()) { - aAdapter.delete(aDocument, currentUser.getUsername(), aCas, new VID(annoFS)); + aAdapter.delete(aDocument, dataOwner.getUsername(), aCas, VID.of(annoFS)); aBulkResult.deleted++; } } @@ -797,30 +785,32 @@ private void deleteAnnotationAtSearchResult(SourceDocument aDocument, CAS aCas, private void writeJCasAndUpdateTimeStamp(SourceDocument aSourceDoc, CAS aCas) throws IOException, AnnotationException { - AnnotatorState state = getModelObject(); + var state = getModelObject(); if (Objects.equals(state.getDocument().getId(), aSourceDoc.getId())) { // Updating the currently open document is done through the page in order to notify the // mechanism to detect concurrent modifications. getAnnotationPage().writeEditorCas(aCas); + return; } - else { - documentService.writeAnnotationCas(aCas, aSourceDoc, currentUser, true); - } + + documentService.writeAnnotationCas(aCas, aSourceDoc, state.getUser().getUsername(), true); } private boolean featureValuesMatchCurrentState(AnnotationFS aAnnotationFS) { - SpanAdapter aAdapter = (SpanAdapter) annotationService + var aAdapter = (SpanAdapter) annotationService .getAdapter(getModelObject().getSelectedAnnotationLayer()); - for (FeatureState state : getModelObject().getFeatureStates()) { - Object featureValue = state.value; - AnnotationFeature feature = state.feature; + for (var state : getModelObject().getFeatureStates()) { + var featureValue = state.value; + var feature = state.feature; + // Ignore slot features - cf. https://github.com/inception-project/inception/issues/2505 if (feature.getLinkMode() != LinkMode.NONE) { continue; } - Object valueAtFS = aAdapter.getFeatureValue(feature, aAnnotationFS); + + var valueAtFS = aAdapter.getFeatureValue(feature, aAnnotationFS); if (!Objects.equals(valueAtFS, featureValue)) { return false; } @@ -838,31 +828,29 @@ public SearchResultGroup(String aId, String aMarkupId, MarkupContainer aMarkupPr { super(aId, aMarkupId, aMarkupProvider, aModel); - ListView statementList = new ListView("results") + var statementList = new ListView("results") { private static final long serialVersionUID = 5811425707843441458L; @Override protected void populateItem(ListItem aItem) { - Project currentProject = SearchAnnotationSidebar.this.getModel().getObject() + var currentProject = SearchAnnotationSidebar.this.getModel().getObject() .getProject(); - SearchResult result = aItem.getModelObject(); - - LambdaAjaxLink lambdaAjaxLink = new LambdaAjaxLink("showSelectedDocument", - t -> { - selectedResult = aItem.getModelObject(); - actionShowSelectedDocument(t, - documentService.getSourceDocument(currentProject, - selectedResult.getDocumentTitle()), - selectedResult.getOffsetStart(), - selectedResult.getOffsetEnd()); - // Need to re-render because we want to highlight the match - getAnnotationPage().actionRefreshDocument(t); - }); + var result = aItem.getModelObject(); + + var lambdaAjaxLink = new LambdaAjaxLink("showSelectedDocument", t -> { + selectedResult = aItem.getModelObject(); + actionShowSelectedDocument(t, + documentService.getSourceDocument(currentProject, + selectedResult.getDocumentTitle()), + selectedResult.getOffsetStart(), selectedResult.getOffsetEnd()); + // Need to re-render because we want to highlight the match + getAnnotationPage().actionRefreshDocument(t); + }); aItem.add(lambdaAjaxLink); - AjaxCheckBox selected = new AjaxCheckBox("selected", + var selected = new AjaxCheckBox("selected", Model.of(result.isSelectedForAnnotation())) { private static final long serialVersionUID = -6955396602403459129L; @@ -870,7 +858,7 @@ protected void populateItem(ListItem aItem) @Override protected void onUpdate(AjaxRequestTarget target) { - SearchResult modelObject = aItem.getModelObject(); + var modelObject = aItem.getModelObject(); modelObject.setSelectedForAnnotation(getModelObject()); if (!getModelObject()) { // not all results in the document are selected, so set document