/*
* Copyright 2012
* 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.project;
import static de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst.CHAIN_TYPE;
import static de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst.COREFERENCE_RELATION_FEATURE;
import static de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst.COREFERENCE_TYPE_FEATURE;
import static de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst.RELATION_TYPE;
import static de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst.SPAN_TYPE;
import static java.util.Arrays.asList;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.uima.cas.CAS;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.extensions.markup.html.form.select.Select;
import org.apache.wicket.extensions.markup.html.form.select.SelectOption;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.ListChoice;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.markup.html.link.DownloadLink;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.springframework.security.core.context.SecurityContextHolder;
import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.clarin.webanno.api.ProjectService;
import de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst;
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.MultiValueMode;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.TagSet;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.clarin.webanno.support.EntityModel;
import de.tudarmstadt.ukp.clarin.webanno.support.JSONUtil;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.settings.ProjectSettingsPanel;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.settings.ProjectSettingsPanelBase;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.SurfaceForm;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token;
/**
* A Panel Used to add Layers to a selected {@link Project} in the project settings page
*/
@ProjectSettingsPanel(label="Layers", prio=300)
public class ProjectLayersPanel
extends ProjectSettingsPanelBase
{
private static final long serialVersionUID = -7870526462864489252L;
private @SpringBean AnnotationSchemaService annotationService;
private @SpringBean ProjectService repository;
private @SpringBean UserDao userRepository;
private final String FIRST = "first";
private final String NEXT = "next";
private LayerSelectionForm layerSelectionForm;
private FeatureSelectionForm featureSelectionForm;
private LayerDetailForm layerDetailForm;
private final FeatureDetailForm featureDetailForm;
private final ImportLayerForm importLayerForm;
private Select<AnnotationLayer> layerSelection;
private final static List<String> PRIMITIVE_TYPES = asList(CAS.TYPE_NAME_STRING,
CAS.TYPE_NAME_INTEGER, CAS.TYPE_NAME_FLOAT, CAS.TYPE_NAME_BOOLEAN);
private List<String> spanTypes = new ArrayList<String>();
private String layerType = WebAnnoConst.SPAN_TYPE;
private List<FileUpload> uploadedFiles;
private FileUploadField fileUpload;
public ProjectLayersPanel(String id, final IModel<Project> aProjectModel)
{
super(id, aProjectModel);
layerSelectionForm = new LayerSelectionForm("layerSelectionForm");
featureSelectionForm = new FeatureSelectionForm("featureSelectionForm");
featureSelectionForm.setVisible(false);
featureSelectionForm.setOutputMarkupPlaceholderTag(true);
layerDetailForm = new LayerDetailForm("layerDetailForm");
layerDetailForm.setVisible(false);
layerDetailForm.setOutputMarkupPlaceholderTag(true);
featureDetailForm = new FeatureDetailForm("featureDetailForm");
featureDetailForm.setVisible(false);
featureDetailForm.setOutputMarkupPlaceholderTag(true);
add(layerSelectionForm);
add(featureSelectionForm);
add(layerDetailForm);
add(featureDetailForm);
importLayerForm = new ImportLayerForm("importLayerForm");
add(importLayerForm);
}
private class LayerSelectionForm
extends Form<SelectionModel>
{
private static final long serialVersionUID = -1L;
public LayerSelectionForm(String id)
{
super(id, new CompoundPropertyModel<SelectionModel>(new SelectionModel()));
add(new Button("create", new StringResourceModel("label"))
{
private static final long serialVersionUID = -4482428496358679571L;
@Override
public void onSubmit()
{
if (ProjectLayersPanel.this.getModelObject().getId() == 0) {
error("Project not yet created. Please save project details first!");
}
else {
LayerSelectionForm.this.getModelObject().layerSelection = null;
layerDetailForm.setModelObject(new AnnotationLayer());
layerDetailForm.setVisible(true);
featureSelectionForm.setVisible(false);
featureDetailForm.setVisible(false);
}
}
});
final Map<AnnotationLayer, String> colors = new HashMap<AnnotationLayer, String>();
layerSelection = new Select<AnnotationLayer>("layerSelection");
ListView<AnnotationLayer> layers = new ListView<AnnotationLayer>("layers",
new LoadableDetachableModel<List<AnnotationLayer>>()
{
private static final long serialVersionUID = 1L;
@Override
protected List<AnnotationLayer> load()
{
Project project = ProjectLayersPanel.this.getModelObject();
if (project.getId() != 0) {
List<AnnotationLayer> layers = annotationService
.listAnnotationLayer(project);
AnnotationLayer tokenLayer = annotationService.getLayer(
Token.class.getName(), project);
layers.remove(tokenLayer);
for (AnnotationLayer layer : layers) {
if (layer.isBuiltIn() && layer.isEnabled()) {
colors.put(layer, "green");
}
else if (layer.isEnabled()) {
colors.put(layer, "blue");
}
else {
colors.put(layer, "red");
}
}
return layers;
}
return new ArrayList<AnnotationLayer>();
}
})
{
private static final long serialVersionUID = 8901519963052692214L;
@Override
protected void populateItem(final ListItem<AnnotationLayer> item)
{
item.add(new SelectOption<AnnotationLayer>("layer", new Model<AnnotationLayer>(
item.getModelObject()))
{
private static final long serialVersionUID = 3095089418860168215L;
@Override
public void onComponentTagBody(MarkupStream markupStream,
ComponentTag openTag)
{
replaceComponentTagBody(markupStream, openTag, item.getModelObject()
.getUiName());
}
}.add(new AttributeModifier("style", "color:"
+ colors.get(item.getModelObject()) + ";")));
}
};
add(layerSelection.add(layers));
layerSelection.setOutputMarkupId(true);
layerSelection.add(new OnChangeAjaxBehavior()
{
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
layerDetailForm.setModelObject(getModelObject().layerSelection);
layerDetailForm.setVisible(true);
LayerSelectionForm.this.setVisible(true);
featureSelectionForm.clearInput();
featureSelectionForm.setVisible(true);
layerDetailForm.setVisible(true);
featureDetailForm.setVisible(false);
layerType = getModelObject().layerSelection.getType();
aTarget.add(layerDetailForm);
aTarget.add(featureSelectionForm);
aTarget.add(featureDetailForm);
}
});
}
}
private class ImportLayerForm
extends Form<String>
{
private static final long serialVersionUID = -7777616763931128598L;
@SuppressWarnings({ "unchecked", "rawtypes" })
public ImportLayerForm(String id)
{
super(id);
add(fileUpload = new FileUploadField("content", new Model()));
add(new Button("import", new StringResourceModel("label"))
{
private static final long serialVersionUID = 1L;
@Override
public void onSubmit()
{
uploadedFiles = fileUpload.getFileUploads();
Project project = ProjectLayersPanel.this.getModelObject();
String username = SecurityContextHolder.getContext().getAuthentication()
.getName();
User user = userRepository.get(username);
if (isEmpty(uploadedFiles)) {
error("Please choose file with layer details before uploading");
return;
}
else if (project.getId() == 0) {
error("Project not yet created, please save project Details!");
return;
}
for (FileUpload tagFile : uploadedFiles) {
InputStream tagInputStream;
try {
tagInputStream = tagFile.getInputStream();
String text = IOUtils.toString(tagInputStream, "UTF-8");
de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationLayer exLayer = JSONUtil
.getJsonConverter()
.getObjectMapper()
.readValue(
text,
de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationLayer.class);
AnnotationLayer attachLayer = null;
if (exLayer.getAttachType() != null) {
de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationLayer exAttachLayer = exLayer
.getAttachType();
createLayer(exAttachLayer, user, null);
attachLayer = annotationService.getLayer(exAttachLayer.getName(),
project);
}
createLayer(exLayer, user, attachLayer);
layerDetailForm.setModelObject(annotationService.getLayer(
exLayer.getName(), project));
layerDetailForm.setVisible(true);
featureSelectionForm.setVisible(true);
}
catch (IOException e) {
error("Error Importing TagSet " + ExceptionUtils.getRootCauseMessage(e));
}
}
featureDetailForm.setVisible(false);
}
private void createLayer(
de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationLayer aExLayer,
User aUser, AnnotationLayer aAttachLayer)
throws IOException
{
Project project = ProjectLayersPanel.this.getModelObject();
AnnotationLayer layer;
if (annotationService.existsLayer(aExLayer.getName(), aExLayer.getType(),
project)) {
layer = annotationService.getLayer(aExLayer.getName(), project);
ImportUtil.setLayer(annotationService, layer, aExLayer, project, aUser);
}
else {
layer = new AnnotationLayer();
ImportUtil.setLayer(annotationService, layer, aExLayer, project, aUser);
}
layer.setAttachType(aAttachLayer);
for (de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationFeature exfeature : aExLayer
.getFeatures()) {
de.tudarmstadt.ukp.clarin.webanno.export.model.TagSet exTagset = exfeature
.getTagSet();
TagSet tagSet = null;
if (exTagset != null
&& annotationService.existsTagSet(exTagset.getName(), project)) {
tagSet = annotationService.getTagSet(exTagset.getName(), project);
ImportUtil.createTagSet(tagSet, exTagset, project, aUser,
annotationService);
}
else if (exTagset != null) {
tagSet = new TagSet();
ImportUtil.createTagSet(tagSet, exTagset, project, aUser,
annotationService);
}
if (annotationService.existsFeature(exfeature.getName(), layer)) {
AnnotationFeature feature = annotationService.getFeature(
exfeature.getName(), layer);
feature.setTagset(tagSet);
ImportUtil.setFeature(annotationService, feature, exfeature, project,
aUser);
continue;
}
AnnotationFeature feature = new AnnotationFeature();
feature.setLayer(layer);
feature.setTagset(tagSet);
ImportUtil
.setFeature(annotationService, feature, exfeature, project, aUser);
}
}
});
}
}
public class SelectionModel
implements Serializable
{
private static final long serialVersionUID = -1L;
private AnnotationLayer layerSelection;
public AnnotationFeature feature;
}
private class LayerDetailForm
extends Form<AnnotationLayer>
{
private static final long serialVersionUID = -1L;
private Label name;
private TextField<String> uiName;
private static final String TYPE_PREFIX = "webanno.custom.";
private String layerName;
private DropDownChoice<String> layerTypes;
private DropDownChoice<AnnotationLayer> attachTypes;
private Label lockToTokenOffsetLabel;
private CheckBox lockToTokenOffset;
private Label allowStackingLabel;
private CheckBox allowStacking;
private Label crossSentenceLabel;
private CheckBox crossSentence;
private Label multipleTokensLabel;
private CheckBox multipleTokens;
private Label linkedListBehaviorLabel;
private CheckBox linkedListBehavior;
public LayerDetailForm(String id)
{
super(id, new CompoundPropertyModel<AnnotationLayer>(new EntityModel<AnnotationLayer>(
new AnnotationLayer())));
final Project project = ProjectLayersPanel.this.getModelObject();
add(uiName = (TextField<String>) new TextField<String>("uiName").setRequired(true));
uiName.add(new AjaxFormComponentUpdatingBehavior("keyup")
{
private static final long serialVersionUID = -1756244972577094229L;
@Override
protected void onUpdate(AjaxRequestTarget target)
{
String modelValue = StringUtils.capitalize(getModelObject().getUiName());
layerName = modelValue;
}
});
add(name = new Label("name")
{
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure()
{
setVisible(StringUtils.isNotBlank(LayerDetailForm.this.getModelObject()
.getName()));
};
});
add(new TextArea<String>("description").setOutputMarkupPlaceholderTag(true));
add(new CheckBox("enabled"));
add(layerTypes = (DropDownChoice<String>) new DropDownChoice<String>("type",
Arrays.asList(new String[] { SPAN_TYPE, RELATION_TYPE, CHAIN_TYPE }))
{
private static final long serialVersionUID = 1244555334843130802L;
@Override
public boolean isEnabled()
{
return LayerDetailForm.this.getModelObject().getId() == 0;
}
}.setRequired(true));
layerTypes.add(new AjaxFormComponentUpdatingBehavior("change")
{
private static final long serialVersionUID = 6790949494089940303L;
@Override
protected void onUpdate(AjaxRequestTarget target)
{
layerType = getModelObject().getType();
target.add(lockToTokenOffsetLabel);
target.add(lockToTokenOffset);
target.add(allowStackingLabel);
target.add(allowStacking);
target.add(crossSentenceLabel);
target.add(crossSentence);
target.add(multipleTokensLabel);
target.add(multipleTokens);
target.add(linkedListBehaviorLabel);
target.add(linkedListBehavior);
target.add(attachTypes);
}
});
attachTypes = (DropDownChoice<AnnotationLayer>) new DropDownChoice<AnnotationLayer>(
"attachType")
{
private static final long serialVersionUID = -6705445053442011120L;
{
setChoices(new LoadableDetachableModel<List<AnnotationLayer>>()
{
private static final long serialVersionUID = 1784646746122513331L;
@Override
protected List<AnnotationLayer> load()
{
List<AnnotationLayer> allLayers = annotationService
.listAnnotationLayer(project);
if (LayerDetailForm.this.getModelObject().getId() > 0) {
if (LayerDetailForm.this.getModelObject().getAttachType() == null) {
return new ArrayList<AnnotationLayer>();
}
return Arrays.asList(LayerDetailForm.this.getModelObject()
.getAttachType());
}
if (!layerType.equals(RELATION_TYPE)) {
return new ArrayList<AnnotationLayer>();
}
List<AnnotationLayer> attachTeypes = new ArrayList<AnnotationLayer>();
// remove a span layer which is already used as attach type for the
// other
List<AnnotationLayer> usedLayers = new ArrayList<AnnotationLayer>();
for (AnnotationLayer layer : allLayers) {
if (layer.getAttachType() != null) {
usedLayers.add(layer.getAttachType());
}
}
allLayers.removeAll(usedLayers);
for (AnnotationLayer layer : allLayers) {
if (layer.getType().equals(SPAN_TYPE) && !layer.isBuiltIn()) {
attachTeypes.add(layer);
}
}
return attachTeypes;
}
});
setChoiceRenderer(new ChoiceRenderer<AnnotationLayer>("uiName"));
}
@Override
protected void onConfigure()
{
setEnabled(LayerDetailForm.this.getModelObject().getId() == 0);
setNullValid(isVisible());
};
};
attachTypes.setOutputMarkupPlaceholderTag(true);
add(attachTypes);
// Behaviors of layers
add(new CheckBox("readonly"));
add(lockToTokenOffsetLabel = new Label("lockToTokenOffsetLabel",
"Lock to token offsets:")
{
private static final long serialVersionUID = -1290883833837327207L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
// Makes no sense for relation layers or layers that attach to tokens
setVisible(!isBlank(layer.getType()) && !RELATION_TYPE.equals(layer.getType())
&& layer.getAttachFeature() == null);
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType()));
}
});
add(lockToTokenOffset = new CheckBox("lockToTokenOffset")
{
private static final long serialVersionUID = -4934708834659137207L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
// Makes no sense for relation layers or layers that attach to tokens
setVisible(!isBlank(layer.getType()) && !RELATION_TYPE.equals(layer.getType())
&& layer.getAttachFeature() == null);
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType()));
}
});
add(allowStackingLabel = new Label("allowStackingLabel", "Allow stacking:")
{
private static final long serialVersionUID = -5354062154610496880L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
setVisible(!isBlank(layer.getType()));
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType()) &&
// Not configurable for layers that attach to tokens (currently that is the
// only layer on which we use the attach feature)
layer.getAttachFeature() == null);
}
});
add(allowStacking = new CheckBox("allowStacking")
{
private static final long serialVersionUID = 7800627916287273008L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
setVisible(!isBlank(layer.getType()));
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType()) &&
// Not configurable for layers that attach to tokens (currently that is the
// only layer on which we use the attach feature)
layer.getAttachFeature() == null);
}
});
add(crossSentenceLabel = new Label("crossSentenceLabel",
"Allow crossing sentence boundary:")
{
private static final long serialVersionUID = -5354062154610496880L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
setVisible(!isBlank(layer.getType()));
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType())
// Not configurable for layers that attach to tokens (currently that
// is the only layer on which we use the attach feature)
&& layer.getAttachFeature() == null);
}
});
add(crossSentence = new CheckBox("crossSentence")
{
private static final long serialVersionUID = -5986386642712152491L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
setVisible(!isBlank(layer.getType()));
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType())
// Not configurable for layers that attach to tokens (currently that
// is the only layer on which we use the attach feature)
&& layer.getAttachFeature() == null);
}
});
add(multipleTokensLabel = new Label("multipleTokensLabel", "Allow multiple tokens:")
{
private static final long serialVersionUID = -5354062154610496880L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
// Makes no sense for relations
setVisible(!isBlank(layer.getType()) && !RELATION_TYPE.equals(layer.getType()));
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType())
// Not configurable for layers that attach to tokens (currently that
// is the only layer on which we use the attach feature)
&& layer.getAttachFeature() == null);
}
});
add(multipleTokens = new CheckBox("multipleTokens")
{
private static final long serialVersionUID = 1319818165277559402L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
// Makes no sense for relations
setVisible(!isBlank(layer.getType()) && !RELATION_TYPE.equals(layer.getType()));
setEnabled(
// Surface form must be locked to token boundaries for CONLL-U writer
// to work.
!SurfaceForm.class.getName().equals(layer.getName()) &&
// Not configurable for chains
!CHAIN_TYPE.equals(layer.getType())
// Not configurable for layers that attach to tokens (currently that
// is the only layer on which we use the attach feature)
&& layer.getAttachFeature() == null);
}
});
add(linkedListBehaviorLabel = new Label("linkedListBehaviorLabel",
"Behave like a linked list:")
{
private static final long serialVersionUID = -5354062154610496880L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
setVisible(!isBlank(layer.getType()) && CHAIN_TYPE.equals(layer.getType()));
}
});
add(linkedListBehavior = new CheckBox("linkedListBehavior")
{
private static final long serialVersionUID = 1319818165277559402L;
{
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
setVisible(!isBlank(layer.getType()) && CHAIN_TYPE.equals(layer.getType()));
}
});
linkedListBehavior.add(new AjaxFormComponentUpdatingBehavior("change")
{
private static final long serialVersionUID = -2904306846882446294L;
@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
featureSelectionForm.updateChoices();
aTarget.add(featureSelectionForm);
aTarget.add(featureDetailForm);
}
});
add(new Button("save", new StringResourceModel("label"))
{
private static final long serialVersionUID = 1L;
@Override
public void onSubmit()
{
AnnotationLayer layer = LayerDetailForm.this.getModelObject();
if (layer.isLockToTokenOffset() && layer.isMultipleTokens()) {
layer.setLockToTokenOffset(false);
}
if (layer.getId() == 0) {
layerName = layerName.replaceAll("\\W", "");
if(layerName.isEmpty() || !isAscii(layerName)){
error("Non ASCII characters can not be used as layer name!");
return;
}
if (annotationService.existsLayer(TYPE_PREFIX + layerName, layer.getType(),
project)) {
error("Only one Layer per project is allowed!");
return;
}
if (layer.getType().equals(RELATION_TYPE) && layer.getAttachType() == null) {
error("a relation layer need an attach type!");
return;
}
if ((TYPE_PREFIX + layerName).endsWith(".")) {
error("please give a proper layer name!");
return;
}
String username = SecurityContextHolder.getContext().getAuthentication()
.getName();
User user = userRepository.get(username);
layer.setProject(project);
try {
layer.setName(TYPE_PREFIX + layerName);
annotationService.createLayer(layer);
if (layer.getType().equals(WebAnnoConst.CHAIN_TYPE)) {
AnnotationFeature relationFeature = new AnnotationFeature();
relationFeature.setType(CAS.TYPE_NAME_STRING);
relationFeature.setName(COREFERENCE_RELATION_FEATURE);
relationFeature.setLayer(layer);
relationFeature.setEnabled(true);
relationFeature.setUiName("Reference Relation");
relationFeature.setProject(project);
annotationService.createFeature(relationFeature);
AnnotationFeature typeFeature = new AnnotationFeature();
typeFeature.setType(CAS.TYPE_NAME_STRING);
typeFeature.setName(COREFERENCE_TYPE_FEATURE);
typeFeature.setLayer(layer);
typeFeature.setEnabled(true);
typeFeature.setUiName("Reference Type");
typeFeature.setProject(project);
annotationService.createFeature(typeFeature);
}
}
catch (IOException e) {
error("unable to create Logger file while creating this layer" + ":"
+ ExceptionUtils.getRootCauseMessage(e));
}
featureSelectionForm.setVisible(true);
}
}
});
add(new DownloadLink("export", new LoadableDetachableModel<File>()
{
private static final long serialVersionUID = 840863954694163375L;
@Override
protected File load()
{
File exportFile = null;
try {
exportFile = File.createTempFile("exportedLayer", ".json");
}
catch (IOException e1) {
error("Unable to create temporary File!!");
}
if (ProjectLayersPanel.this.getModelObject().getId() == 0) {
error("Project not yet created. Please save project details first!");
return null;
}
AnnotationLayer layer = layerDetailForm.getModelObject();
de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationLayer exLayer = ImportUtil
.exportLayerDetails(null, null, layer, annotationService);
if (layer.getAttachType() != null) {
AnnotationLayer attachLayer = layer.getAttachType();
de.tudarmstadt.ukp.clarin.webanno.export.model.AnnotationLayer exAttachLayer = ImportUtil
.exportLayerDetails(null, null, attachLayer, annotationService);
exLayer.setAttachType(exAttachLayer);
}
try {
JSONUtil.generatePrettyJson(exLayer, exportFile);
}
catch (IOException e) {
error("File Path not found or No permision to save the file!");
}
info("TagSets successfully exported to :" + exportFile.getAbsolutePath());
return exportFile;
}
}).setDeleteAfterDownload(true).setOutputMarkupId(true));
add(new Button("cancel", new StringResourceModel("label")) {
private static final long serialVersionUID = 1L;
{
// Avoid saving data
setDefaultFormProcessing(false);
setVisible(true);
}
@Override
public void onSubmit()
{
// layerDetailForm.setModelObject(new AnnotationLayer());
// layerSelectionForm.setModelObject(new SelectionModel());
// featureSelectionForm.setModelObject(new SelectionModel());
// featureDetailForm.setModelObject(new AnnotationFeature());
// layerDetailForm.setModelObject(null);
layerDetailForm.setVisible(false);
featureSelectionForm.setVisible(false);
featureDetailForm.setVisible(false);
}
});
}
}
public static boolean isAscii(String v)
{
CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
return asciiEncoder.canEncode(v);
}
private class FeatureDetailForm
extends Form<AnnotationFeature>
{
private static final long serialVersionUID = -1L;
DropDownChoice<TagSet> tagSet;
DropDownChoice<String> featureType;
CheckBox required;
List<String> types = new ArrayList<String>();
public FeatureDetailForm(String id)
{
super(id, new CompoundPropertyModel<AnnotationFeature>(
new EntityModel<AnnotationFeature>(new AnnotationFeature())));
add(new Label("name")
{
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure()
{
setVisible(StringUtils.isNotBlank(FeatureDetailForm.this.getModelObject()
.getName()));
};
});
add(new TextField<String>("uiName").setRequired(true));
add(new TextArea<String>("description").setOutputMarkupPlaceholderTag(true));
add(new CheckBox("enabled"));
add(new CheckBox("visible"));
add(new CheckBox("remember"));
add(required = new CheckBox("required") {
private static final long serialVersionUID = -2716373442353375910L;
{
setOutputMarkupId(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
boolean relevant = CAS.TYPE_NAME_STRING
.equals(FeatureDetailForm.this.getModelObject().getType());
setEnabled(relevant);
if (!relevant) {
FeatureDetailForm.this.getModelObject().setRequired(false);
}
}
});
add(new CheckBox("hideUnconstraintFeature"));
// spanTypes.add(CAS.TYPE_NAME_ANNOTATION);
// for (AnnotationLayer spanLayer : annotationService
// .listAnnotationLayer(selectedProjectModel.getObject())) {
// if (spanLayer.getName().equals(Token.class.getName())) {
// continue;
// }
// if (spanLayer.getType().equals(WebAnnoConst.SPAN_TYPE)) {
// spanTypes.add(spanLayer.getName());
// }
// }
add(featureType = (DropDownChoice<String>) new DropDownChoice<String>("type")
{
private static final long serialVersionUID = 9029205407108101183L;
{
setRequired(true);
setNullValid(false);
setChoices(new LoadableDetachableModel<List<String>>()
{
private static final long serialVersionUID = -5732558926576750673L;
@Override
protected List<String> load()
{
if (getModelObject() != null) {
return Arrays.asList(getModelObject());
}
// types.addAll(spanTypes);
return types;
}
});
}
@Override
protected void onConfigure()
{
setEnabled(FeatureDetailForm.this.getModelObject().getId() == 0);
};
});
featureType.add(new AjaxFormComponentUpdatingBehavior("change")
{
private static final long serialVersionUID = -2904306846882446294L;
@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
aTarget.add(tagSet);
aTarget.add(required);
}
});
add(tagSet = new DropDownChoice<TagSet>("tagset")
{
private static final long serialVersionUID = -6705445053442011120L;
{
setOutputMarkupPlaceholderTag(true);
setOutputMarkupId(true);
setChoiceRenderer(new ChoiceRenderer<TagSet>("name"));
setNullValid(true);
setChoices(new LoadableDetachableModel<List<TagSet>>()
{
private static final long serialVersionUID = 1784646746122513331L;
@Override
protected List<TagSet> load()
{
return annotationService
.listTagSets(ProjectLayersPanel.this.getModelObject());
}
});
}
@Override
protected void onConfigure()
{
AnnotationFeature feature = FeatureDetailForm.this.getModelObject();
// Only display tagset choice for link features with role
// and string features
// Since we currently set the LinkRole only when saving, we
// have to rely on the
// feature type here.
setEnabled(CAS.TYPE_NAME_STRING.equals(feature.getType())
|| !PRIMITIVE_TYPES.contains(feature.getType()));
//Empty the 'type' list drop-down in FeatureDetailForm
types.clear();
//Add primitive types
types.addAll(PRIMITIVE_TYPES);
// Add non-primitive types only when layer is of type SPAN (#62)
if (layerDetailForm.getModelObject().getType().equals(WebAnnoConst.SPAN_TYPE)) {
spanTypes.add(CAS.TYPE_NAME_ANNOTATION);
//Add layers of type SPAN available in the project
for (AnnotationLayer spanLayer : annotationService
.listAnnotationLayer(ProjectLayersPanel.this.getModelObject())) {
if (spanLayer.getType().equals(WebAnnoConst.SPAN_TYPE)) {
types.add(spanLayer.getName());
}
}
}
}
});
add(new Button("save", new StringResourceModel("label"))
{
private static final long serialVersionUID = 1L;
@Override
public void onSubmit()
{
AnnotationFeature feature = FeatureDetailForm.this.getModelObject();
String name = feature.getUiName();
name = name.replaceAll("\\W", "");
// Check if feature name is not from the restricted names list
if (WebAnnoConst.RESTRICTED_FEATURE_NAMES.contains(name)) {
error("'" + feature.getUiName().toLowerCase() + " (" + name + ")"
+ "' is a restricted keyword for a feature name. Please use a different name for the feature.");
return;
}
if (layerDetailForm.getModelObject().getType().equals(RELATION_TYPE)
&& (name.equals(WebAnnoConst.FEAT_REL_SOURCE)
|| name.equals(WebAnnoConst.FEAT_REL_TARGET)
|| name.equals(FIRST) || name.equals(NEXT))) {
error("layer " + name + " is not allowed as a feature name");
return;
}
// Checking if feature name doesn't start with a number or underscore
// And only uses alphanumeric characters
if (StringUtils.isNumeric(name.substring(0, 1))
|| name.substring(0, 1).equals("_")
|| !StringUtils.isAlphanumeric(name.replace("_", ""))) {
error("Feature names must start with a letter and consist only of letters, digits, or underscores.");
return;
}
if (feature.getId() == 0) {
feature.setLayer(layerDetailForm.getModelObject());
feature.setProject(ProjectLayersPanel.this.getModelObject());
if (annotationService.existsFeature(feature.getName(), feature.getLayer())) {
error("This feature is already added for this layer!");
return;
}
if (annotationService.existsFeature(name, feature.getLayer())) {
error("this feature already exists!");
return;
}
feature.setName(name);
saveFeature(feature);
}
if (tagSet.getModelObject() != null) {
FeatureDetailForm.this.getModelObject().setTagset(tagSet.getModelObject());
}
}
});
add(new Button("cancel", new StringResourceModel("label")) {
private static final long serialVersionUID = 1L;
{
// Avoid saving data
setDefaultFormProcessing(false);
setVisible(true);
}
@Override
public void onSubmit()
{
featureDetailForm.setModelObject(new AnnotationFeature());
// FeatureDetailForm.this.setVisible(false);
}
});
}
}
private void saveFeature(AnnotationFeature aFeature)
{
// Set properties of link features since these are currently not configurable in the UI
if (!PRIMITIVE_TYPES.contains(aFeature.getType())) {
aFeature.setMode(MultiValueMode.ARRAY);
aFeature.setLinkMode(LinkMode.WITH_ROLE);
aFeature.setLinkTypeRoleFeatureName("role");
aFeature.setLinkTypeTargetFeatureName("target");
aFeature.setLinkTypeName(aFeature.getLayer().getName()
+ WordUtils.capitalize(aFeature.getName()) + "Link");
}
// If the feature is not a string feature or a link-with-role feature, force the tagset
// to null.
if (!(CAS.TYPE_NAME_STRING.equals(aFeature.getType()) || !PRIMITIVE_TYPES.contains(aFeature
.getType()))) {
aFeature.setTagset(null);
}
annotationService.createFeature(aFeature);
featureDetailForm.setVisible(false);
}
public class FeatureSelectionForm
extends Form<SelectionModel>
{
private static final long serialVersionUID = -1L;
private ListChoice<AnnotationFeature> feature;
public FeatureSelectionForm(String id)
{
super(id, new CompoundPropertyModel<SelectionModel>(new SelectionModel()));
add(feature = new ListChoice<AnnotationFeature>("feature")
{
private static final long serialVersionUID = 1L;
{
setChoices(regenerateModel());
setChoiceRenderer(new ChoiceRenderer<AnnotationFeature>()
{
private static final long serialVersionUID = 4610648616450168333L;
@Override
public Object getDisplayValue(AnnotationFeature aObject)
{
return aObject.getUiName() + " : ["
+ StringUtils.substringAfterLast(aObject.getType(), ".") + "]";
}
});
setNullValid(false);
}
@Override
protected void onSelectionChanged(AnnotationFeature aNewSelection)
{
if (aNewSelection != null) {
featureDetailForm.setModelObject(aNewSelection);
featureDetailForm.setVisible(true);
}
}
@Override
protected boolean wantOnSelectionChangedNotifications()
{
return true;
}
@Override
protected CharSequence getDefaultChoice(String aSelectedValue)
{
return "";
}
});
add(new Button("new", new StringResourceModel("label"))
{
private static final long serialVersionUID = 1L;
@Override
public void onSubmit()
{
featureDetailForm.setDefaultModelObject(new AnnotationFeature());
featureDetailForm.setVisible(true);
}
@Override
public boolean isEnabled()
{
return layerDetailForm.getModelObject() != null
&& !layerDetailForm.getModelObject().isBuiltIn()
&& !layerDetailForm.getModelObject().getType().equals(CHAIN_TYPE);
}
});
}
private LoadableDetachableModel<List<AnnotationFeature>> regenerateModel()
{
return new LoadableDetachableModel<List<AnnotationFeature>>()
{
private static final long serialVersionUID = 1L;
@Override
protected List<AnnotationFeature> load()
{
List<AnnotationFeature> features = annotationService
.listAnnotationFeature(layerDetailForm.getModelObject());
if (CHAIN_TYPE.equals(layerDetailForm.getModelObject().getType())
&& !layerDetailForm.getModelObject().isLinkedListBehavior()) {
List<AnnotationFeature> filtered = new ArrayList<AnnotationFeature>();
for (AnnotationFeature f : features) {
if (!WebAnnoConst.COREFERENCE_RELATION_FEATURE.equals(f.getName())) {
filtered.add(f);
}
}
return filtered;
}
else {
return features;
}
}
};
}
public void updateChoices()
{
feature.setChoices(regenerateModel());
}
}
}