/*
* Copyright 2017
* Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology
* Technische Universität Darmstadt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.ui.annotation;
import static org.apache.uima.fit.util.CasUtil.select;
import java.io.IOException;
import java.util.List;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.jcas.JCas;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.slf4j.LoggerFactory;
import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.clarin.webanno.api.DocumentService;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.adapter.TypeAdapter;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.AnnotatorState;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.TypeUtil;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.support.dialog.ChallengeResponseDialog;
import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxLink;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ApplicationPageBase;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence;
public abstract class AnnotationPageBase
extends ApplicationPageBase
{
private static final long serialVersionUID = -1133219266479577443L;
private @SpringBean AnnotationSchemaService annotationService;
private @SpringBean DocumentService documentService;
private ChallengeResponseDialog resetDocumentDialog;
private LambdaAjaxLink resetDocumentLink;
private Label numberOfPages;
protected AnnotationPageBase()
{
super();
}
protected AnnotationPageBase(PageParameters aParameters)
{
super(aParameters);
}
public void setModel(IModel<AnnotatorState> aModel)
{
setDefaultModel(aModel);
}
@SuppressWarnings("unchecked")
public IModel<AnnotatorState> getModel()
{
return (IModel<AnnotatorState>) getDefaultModel();
}
public void setModelObject(AnnotatorState aModel)
{
setDefaultModelObject(aModel);
}
public AnnotatorState getModelObject()
{
return (AnnotatorState) getDefaultModelObject();
}
protected Label getOrCreatePositionInfoLabel()
{
if (numberOfPages == null) {
numberOfPages = new Label("numberOfPages",
new StringResourceModel("PositionInfo.text", this).setModel(getModel())
.setParameters(
PropertyModel.of(getModel(), "firstVisibleSentenceNumber"),
PropertyModel.of(getModel(), "lastVisibleSentenceNumber"),
PropertyModel.of(getModel(), "numberOfSentences"),
PropertyModel.of(getModel(), "documentIndex"),
PropertyModel.of(getModel(), "numberOfDocuments")))
{
private static final long serialVersionUID = 7176610419683776917L;
{
setOutputMarkupId(true);
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
setVisible(getModelObject().getDocument() != null);
}
};
}
return numberOfPages;
}
protected ChallengeResponseDialog createOrGetResetDocumentDialog()
{
if (resetDocumentDialog == null) {
IModel<String> documentNameModel = PropertyModel.of(getModel(), "document.name");
resetDocumentDialog = new ChallengeResponseDialog("resetDocumentDialog",
new StringResourceModel("ResetDocumentDialog.title", this),
new StringResourceModel("ResetDocumentDialog.text", this).setModel(getModel())
.setParameters(documentNameModel),
documentNameModel);
resetDocumentDialog.setConfirmAction(this::actionResetDocument);
}
return resetDocumentDialog;
}
protected LambdaAjaxLink createOrGetResetDocumentLink()
{
if (resetDocumentLink == null) {
resetDocumentLink = new LambdaAjaxLink("showResetDocumentDialog",
t -> { resetDocumentDialog.show(t); })
{
private static final long serialVersionUID = 874573384012299998L;
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotatorState state = AnnotationPageBase.this.getModelObject();
setEnabled(state.getDocument() != null && !documentService
.isAnnotationFinished(state.getDocument(), state.getUser()));
}
};
resetDocumentLink.setOutputMarkupId(true);
}
return resetDocumentLink;
}
/**
* Show the previous document, if exist
*/
protected void actionShowPreviousDocument(AjaxRequestTarget aTarget)
{
getModelObject().moveToPreviousDocument(getListOfDocs());
actionLoadDocument(aTarget);
}
/**
* Show the next document if exist
*/
protected void actionShowNextDocument(AjaxRequestTarget aTarget)
{
getModelObject().moveToNextDocument(getListOfDocs());
actionLoadDocument(aTarget);
}
protected void actionShowPreviousPage(AjaxRequestTarget aTarget)
throws Exception
{
JCas jcas = getEditorCas();
getModelObject().moveToPreviousPage(jcas);
actionRefreshDocument(aTarget, jcas);
}
protected void actionShowNextPage(AjaxRequestTarget aTarget)
throws Exception
{
JCas jcas = getEditorCas();
getModelObject().moveToNextPage(jcas);
actionRefreshDocument(aTarget, jcas);
}
protected void actionShowFirstPage(AjaxRequestTarget aTarget)
throws Exception
{
JCas jcas = getEditorCas();
getModelObject().moveToFirstPage(jcas);
actionRefreshDocument(aTarget, jcas);
}
protected void actionShowLastPage(AjaxRequestTarget aTarget)
throws Exception
{
JCas jcas = getEditorCas();
getModelObject().moveToLastPage(jcas);
actionRefreshDocument(aTarget, jcas);
}
protected void actionResetDocument(AjaxRequestTarget aTarget)
throws Exception
{
AnnotatorState state = getModelObject();
JCas jcas = documentService.createOrReadInitialCas(state.getDocument());
documentService.writeAnnotationCas(jcas, state.getDocument(), state.getUser(), false);
actionLoadDocument(aTarget);
}
protected void handleException(AjaxRequestTarget aTarget, Exception aException)
{
LoggerFactory.getLogger(getClass()).error("Error: " + aException.getMessage(), aException);
error("Error: " + aException.getMessage());
if (aTarget != null) {
aTarget.addChildren(getPage(), FeedbackPanel.class);
}
}
protected abstract List<SourceDocument> getListOfDocs();
protected abstract JCas getEditorCas()
throws IOException;
/**
* Open a document or to a different document. This method should be used only the first time
* that a document is accessed. It reset the annotator state and upgrades the CAS.
*/
protected abstract void actionLoadDocument(AjaxRequestTarget aTarget);
/**
* Re-render the document and update all related UI elements.
*
* This method should be used while the editing process is ongoing. It does not upgrade the CAS
* and it does not reset the annotator state.
*/
protected abstract void actionRefreshDocument(AjaxRequestTarget aTarget, JCas aJcas);
/**
* Checks if all required features on all annotations are set. If a required feature value is
* missing, then the method scrolls to that location and schedules a re-rendering. In such
* a case, an {@link IllegalStateException} is thrown.
*/
protected void ensureRequiredFeatureValuesSet(AjaxRequestTarget aTarget, JCas aJcas)
{
AnnotatorState state = getModelObject();
JCas editorJCas = aJcas;
CAS editorCas = editorJCas.getCas();
for (AnnotationLayer layer : annotationService.listAnnotationLayer(state.getProject())) {
TypeAdapter adapter = TypeUtil.getAdapter(annotationService, layer);
List<AnnotationFeature> features = annotationService.listAnnotationFeature(layer);
// If no feature is required, then we can skip the whole procedure
if (features.stream().allMatch((f) -> !f.isRequired())) {
continue;
}
// Check each feature structure of this layer
for (AnnotationFS fs : select(editorCas, adapter.getAnnotationType(editorCas))) {
for (AnnotationFeature f : features) {
if (WebAnnoCasUtil.isRequiredFeatureMissing(f, fs)) {
// Find the sentence that contains the annotation with the missing
// required feature value
Sentence s = WebAnnoCasUtil.getSentence(editorJCas, fs.getBegin());
// Put this sentence into the focus
state.setFirstVisibleSentence(s);
actionRefreshDocument(aTarget, editorJCas);
// Inform the user
throw new IllegalStateException(
"Document cannot be marked as finished. Annotation with ID ["
+ WebAnnoCasUtil.getAddr(fs) + "] on layer ["
+ layer.getUiName() + "] is missing value for feature ["
+ f.getUiName() + "].");
}
}
}
}
}
}