Skip to content

Commit

Permalink
Merge pull request #4811 from inception-project/feature/1319-Cross-La…
Browse files Browse the repository at this point in the history
…yer-Relations

#1319 - Cross-Layer Relations
  • Loading branch information
reckart authored May 19, 2024
2 parents 31205af + 40d0e43 commit eedbd14
Show file tree
Hide file tree
Showing 21 changed files with 210 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package de.tudarmstadt.ukp.clarin.webanno.api.annotation.page;

import static de.tudarmstadt.ukp.clarin.webanno.model.ValidationMode.NEVER;
import static de.tudarmstadt.ukp.inception.rendering.selection.FocusPosition.CENTERED;
import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER;
import static java.lang.String.format;
Expand Down Expand Up @@ -56,7 +57,6 @@
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.preferences.UserPreferencesService;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.model.ValidationMode;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase;
Expand Down Expand Up @@ -410,7 +410,7 @@ public void actionValidateDocument(AjaxRequestTarget aTarget, CAS aCas)
continue;
}

if (ValidationMode.NEVER.equals(layer.getValidationMode())) {
if (layer.getValidationMode() == NEVER) {
// If validation is disabled, then skip it
continue;
}
Expand Down
5 changes: 0 additions & 5 deletions inception/inception-diam-editor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@
<artifactId>inception-support</artifactId>
</dependency>

<dependency>
<groupId>org.apache.uima</groupId>
<artifactId>uimaj-core</artifactId>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@
import org.apache.wicket.spring.injection.annot.SpringBean;

import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage;
import de.tudarmstadt.ukp.inception.diam.editor.DiamAjaxBehavior;
import de.tudarmstadt.ukp.inception.diam.editor.DiamJavaScriptReference;
import de.tudarmstadt.ukp.inception.diam.editor.actions.ShowContextMenuHandler;
import de.tudarmstadt.ukp.inception.diam.model.compactv2.CompactSerializerV2Impl;
import de.tudarmstadt.ukp.inception.diam.model.websocket.ViewportDefinition;
import de.tudarmstadt.ukp.inception.editor.AnnotationEditorExtensionRegistry;
Expand Down Expand Up @@ -71,11 +69,7 @@ protected void onInitialize()
{
super.onInitialize();

var page = findParent(AnnotationPage.class);

add(diamBehavior = createDiamBehavior());
diamBehavior.addPriorityHandler(new ShowContextMenuHandler(extensionRegistry,
page.getModel(), page.getAnnotationActionHandler(), page::getEditorCas));
add(new SvelteBehavior());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.core.annotation.Order;

import de.tudarmstadt.ukp.clarin.webanno.api.annotation.exception.IllegalPlacementException;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.exception.NotEditableException;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer;
import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainAdapter;
Expand Down Expand Up @@ -91,13 +92,23 @@ private void actionArc(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget,
IRequestParameters aParams)
throws IOException, AnnotationException
{
var page = getPage();

page.ensureIsEditable();

var originSpan = VID.parse(aParams.getParameterValue(PARAM_ORIGIN_SPAN_ID).toString());
var targetSpan = VID.parse(aParams.getParameterValue(PARAM_TARGET_SPAN_ID).toString());

var cm = aBehavior.getContextMenu();
var clientX = cm.getClientX().getAsInt();
var clientY = cm.getClientY().getAsInt();

actionArc(aBehavior, aTarget, originSpan, targetSpan, clientX, clientY);
}

public void actionArc(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget, VID originSpan,
VID targetSpan, int aClientX, int aClientY)
throws NotEditableException, IOException, AnnotationException, IllegalPlacementException
{
var page = getPage();
page.ensureIsEditable();

if (originSpan.isSynthetic() || targetSpan.isSynthetic()) {
page.error("Relations cannot be created from/to synthetic annotations");
aTarget.addChildren(page, IFeedback.class);
Expand All @@ -114,7 +125,8 @@ private void actionArc(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget,
createChainLink(chainAdapter, aTarget, originSpan, targetSpan);
}
else {
createRelationAnnotation(aBehavior, aTarget, originLayer, originSpan, targetSpan);
createRelationAnnotation(aBehavior, aTarget, originLayer, originSpan, targetSpan,
aClientX, aClientY);
}
}

Expand Down Expand Up @@ -144,7 +156,8 @@ private void createChainLink(ChainAdapter chainAdapter, AjaxRequestTarget aTarge
}

private void createRelationAnnotation(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget,
AnnotationLayer originLayer, VID aOriginSpan, VID aTargetSpan)
AnnotationLayer originLayer, VID aOriginSpan, VID aTargetSpan, int aClientX,
int aClientY)
throws AnnotationException, IllegalPlacementException, IOException
{
var candidateLayers = schemaService.getRelationLayersFor(originLayer);
Expand Down Expand Up @@ -173,7 +186,7 @@ private void createRelationAnnotation(DiamAjaxBehavior aBehavior, AjaxRequestTar
}));
}

cm.onOpen(aTarget);
cm.onOpen(aTarget, aClientX, aClientY);
}

private void createRelationAnnotation(AjaxRequestTarget aTarget, AnnotationLayer relationLayer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@
import org.apache.wicket.request.Request;
import org.springframework.core.annotation.Order;

import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase;
import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainAdapter;
import de.tudarmstadt.ukp.inception.annotation.layer.span.CreateSpanAnnotationRequest;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter;
import de.tudarmstadt.ukp.inception.diam.editor.DiamAjaxBehavior;
import de.tudarmstadt.ukp.inception.diam.editor.config.DiamAutoConfig;
import de.tudarmstadt.ukp.inception.diam.model.ajax.DefaultAjaxResponse;
import de.tudarmstadt.ukp.inception.diam.model.compact.CompactRangeList;
import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState;
import de.tudarmstadt.ukp.inception.rendering.model.Range;
import de.tudarmstadt.ukp.inception.rendering.selection.Selection;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VID;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;
import de.tudarmstadt.ukp.inception.support.json.JSONUtil;

/**
Expand All @@ -47,26 +55,52 @@ public class CreateSpanAnnotationHandler
{
public static final String COMMAND = "spanOpenDialog";

private final AnnotationSchemaService schemaService;

public CreateSpanAnnotationHandler(AnnotationSchemaService aSchemaService)
{
schemaService = aSchemaService;
}

@Override
public String getCommand()
{
return COMMAND;
}

@Override
public DefaultAjaxResponse handle(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget, Request aRequest)
public DefaultAjaxResponse handle(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget,
Request aRequest)
{
try {
var page = getPage();

page.ensureIsEditable();

var cas = page.getEditorCas();
var state = getAnnotatorState();
var range = getRangeFromRequest(state, aRequest.getRequestParameters(), cas);

state.getSelection().selectSpan(cas, range.getBegin(), range.getEnd());
page.getAnnotationActionHandler().actionCreateOrUpdate(aTarget, cas);
var adapter = schemaService.getAdapter(state.getDefaultAnnotationLayer());

var request = new CreateSpanAnnotationRequest(state.getDocument(),
state.getUser().getUsername(), cas, range.getBegin(), range.getEnd());

Selection selection;
if (adapter instanceof SpanAdapter spanAdapter) {
var ann = spanAdapter.handle(request);
selection = adapter.select(VID.of(ann), ann);
}
else if (adapter instanceof ChainAdapter chainAdapter) {
var ann = chainAdapter.handle(request);
selection = adapter.select(VID.of(ann), ann);
}
else {
throw new AnnotationException("Cannot create span annotation on ["
+ state.getDefaultAnnotationLayer().getUiName() + "] of type ["
+ state.getDefaultAnnotationLayer().getType() + "]");
}

commitAnnotation(aTarget, page, state, selection);

return new DefaultAjaxResponse(getAction(aRequest));
}
Expand All @@ -85,12 +119,20 @@ static Range getRangeFromRequest(AnnotatorState aState, IRequestParameters reque
{
var offsets = request.getParameterValue(PARAM_OFFSETS).toString();

var offsetLists = JSONUtil.getObjectMapper().readValue(offsets, CompactRangeList.class);
var offsetLists = JSONUtil.fromJsonString(CompactRangeList.class, offsets);

int annotationBegin = aState.getWindowBeginOffset() + offsetLists.get(0).getBegin();
int annotationEnd = aState.getWindowBeginOffset()
+ offsetLists.get(offsetLists.size() - 1).getEnd();
var begin = aState.getWindowBeginOffset() + offsetLists.get(0).getBegin();
var end = aState.getWindowBeginOffset() + offsetLists.get(offsetLists.size() - 1).getEnd();

return rangeClippedToDocument(aCas, annotationBegin, annotationEnd);
return rangeClippedToDocument(aCas, begin, end);
}

private void commitAnnotation(AjaxRequestTarget aTarget, AnnotationPageBase page,
AnnotatorState state, Selection selection)
throws IOException, AnnotationException
{
state.getSelection().set(selection);
page.getAnnotationActionHandler().actionSelect(aTarget);
page.getAnnotationActionHandler().writeEditorCas();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
public interface EditorAjaxRequestHandler
extends Extension<Request>
{
int PRIO_CONTEXT_MENU = -10;
int PRIO_RENDER_HANDLER = 0;
int PRIO_SLOT_FILLER_HANDLER = 100;
int PRIO_UNARM_SLOT_HANDLER = 180;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,30 @@
*/
package de.tudarmstadt.ukp.inception.diam.editor.actions;

import static de.tudarmstadt.ukp.inception.support.uima.ICasUtil.selectAnnotationByAddr;
import static de.tudarmstadt.ukp.inception.support.spring.ApplicationContextProvider.getApplicationContext;

import java.io.IOException;
import java.io.Serializable;

import org.apache.uima.cas.CAS;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.Request;
import org.springframework.core.annotation.Order;

import de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasProvider;
import de.tudarmstadt.ukp.inception.diam.editor.DiamAjaxBehavior;
import de.tudarmstadt.ukp.inception.diam.model.ajax.AjaxResponse;
import de.tudarmstadt.ukp.inception.diam.model.ajax.DefaultAjaxResponse;
import de.tudarmstadt.ukp.inception.editor.AnnotationEditorExtensionRegistry;
import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler;
import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VID;
import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;
import de.tudarmstadt.ukp.inception.support.lambda.LambdaMenuItem;

@Order(EditorAjaxRequestHandler.PRIO_CONTEXT_MENU)
public class ShowContextMenuHandler
extends EditorAjaxRequestHandlerBase
implements Serializable
{
private static final long serialVersionUID = 2566256640285857435L;

private final AnnotationEditorExtensionRegistry extensionRegistry;
private final IModel<AnnotatorState> model;
private final AnnotationActionHandler actionHandler;
private final CasProvider casProvider;

public ShowContextMenuHandler(
AnnotationEditorExtensionRegistry aAnnotationEditorExtensionRegistry,
IModel<AnnotatorState> aState, AnnotationActionHandler aActionHandler,
CasProvider aCasProvider)
{
extensionRegistry = aAnnotationEditorExtensionRegistry;
model = aState;
actionHandler = aActionHandler;
casProvider = aCasProvider;
}

@Override
public String getCommand()
{
Expand All @@ -83,14 +64,24 @@ public AjaxResponse handle(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget
return new DefaultAjaxResponse(getAction(aRequest));
}

var clientX = cm.getClientX().getAsInt();
var clientY = cm.getClientY().getAsInt();

// Need to fetch this here since the handler is not serializable
var extensionRegistry = getApplicationContext()
.getBean(AnnotationEditorExtensionRegistry.class);

try {

var items = cm.getItemList();
items.clear();

if (model.getObject().getSelection().isSpan()) {
var state = getAnnotatorState();

if (state.getSelection().isSpan()) {
var vid = getVid(aRequest);
items.add(new LambdaMenuItem("Link to ...", $ -> actionLinkTo($, vid)));
items.add(new LambdaMenuItem("Link to ...",
$ -> actionLinkTo(aBehavior, $, vid, clientX, clientY)));
}

extensionRegistry.generateContextMenuItems(items);
Expand All @@ -106,37 +97,24 @@ public AjaxResponse handle(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget
return new DefaultAjaxResponse(getAction(aRequest));
}

private void actionLinkTo(AjaxRequestTarget aTarget, VID paramId)
private void actionLinkTo(DiamAjaxBehavior aBehavior, AjaxRequestTarget aTarget, VID paramId,
int aClientX, int aClientY)
throws IOException, AnnotationException
{
var page = getPage();
page.ensureIsEditable();

var state = model.getObject();
var state = getAnnotatorState();

if (!state.getSelection().isSpan()) {
return;
}

CAS cas;
try {
cas = casProvider.get();
}
catch (Exception e) {
handleError("Unable to load data", e);
return;
}

// Currently selected span
var originFs = selectAnnotationByAddr(cas, state.getSelection().getAnnotation().getId());

// Target span of the relation
var targetFs = selectAnnotationByAddr(cas, paramId.getId());

var selection = state.getSelection();
selection.selectArc(VID.NONE_ID, originFs, targetFs);
// Need to fetch this here since the handler is not serializable
var createRelationAnnotationHandler = getApplicationContext()
.getBean(CreateRelationAnnotationHandler.class);

// Create new annotation
actionHandler.actionCreateOrUpdate(aTarget, cas);
createRelationAnnotationHandler.actionArc(aBehavior, aTarget,
state.getSelection().getAnnotation(), paramId, aClientX, aClientY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import de.tudarmstadt.ukp.inception.diam.editor.actions.SavePreferences;
import de.tudarmstadt.ukp.inception.diam.editor.actions.ScrollToHandler;
import de.tudarmstadt.ukp.inception.diam.editor.actions.SelectAnnotationHandler;
import de.tudarmstadt.ukp.inception.diam.editor.actions.ShowContextMenuHandler;
import de.tudarmstadt.ukp.inception.diam.editor.lazydetails.LazyDetailsLookupService;
import de.tudarmstadt.ukp.inception.diam.editor.lazydetails.LazyDetailsLookupServiceImpl;
import de.tudarmstadt.ukp.inception.diam.model.compact.CompactSerializer;
Expand Down Expand Up @@ -86,9 +87,10 @@ public ExtensionActionHandler extensionActionHandler(
}

@Bean
public CreateSpanAnnotationHandler createSpanAnnotationHandler()
public CreateSpanAnnotationHandler createSpanAnnotationHandler(
AnnotationSchemaService aSchemaService)
{
return new CreateSpanAnnotationHandler();
return new CreateSpanAnnotationHandler(aSchemaService);
}

@Bean
Expand All @@ -106,6 +108,12 @@ public CreateRelationAnnotationHandler createRelationAnnotationHandler(
return new CreateRelationAnnotationHandler(aSchemaService, aAnnotationSchemaProperties);
}

@Bean
public ShowContextMenuHandler ShowContextMenuHandler()
{
return new ShowContextMenuHandler();
}

@Bean
public DeleteAnnotationHandler deleteAnnotationHandler(
AnnotationSchemaService aAnnotationService)
Expand Down
Loading

0 comments on commit eedbd14

Please sign in to comment.