Skip to content

Commit

Permalink
Merge pull request #5322 from inception-project/feature/5321-Add-comm…
Browse files Browse the repository at this point in the history
…ent-feature-initializer

#5321 - Add comment feature initializer
  • Loading branch information
reckart authored Mar 2, 2025
2 parents 9843691 + 087fbc3 commit 3ee308e
Show file tree
Hide file tree
Showing 16 changed files with 537 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void setup()
repositoryProperties.setPath(tempDir);

projectService = new ProjectServiceImpl(userService, applicationEventPublisher,
repositoryProperties, emptyList(), null);
repositoryProperties, emptyList(), emptyList(), null);

sut = new ProjectLogExporter(projectService);
}
Expand Down
5 changes: 5 additions & 0 deletions inception/inception-project-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-support</artifactId>
</dependency>

<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-core</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* 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.inception.project.api;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

import org.apache.wicket.request.resource.ResourceReference;

import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer;

public interface FeatureInitializer
{
String getName();

boolean alreadyApplied(AnnotationLayer aLayer);

List<Class<? extends ProjectInitializer>> getDependencies();

default Optional<String> getDescription()
{
return Optional.empty();
}

default Optional<ResourceReference> getThumbnail()
{
return Optional.empty();
}

void configure(AnnotationLayer aLayer) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ default void initializeProject(Project aProject, List<ProjectInitializer> aIniti

List<ProjectInitializer> listProjectInitializers();

List<FeatureInitializer> listFeatureInitializers();

static MDCContext withProjectLogger(Project aProject)
{
Validate.notNull(aProject, "Project must be given");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* 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.inception.project.initializers.basic;

import static java.util.Arrays.asList;
import static org.apache.uima.cas.CAS.TYPE_NAME_STRING;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Optional;

import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;

import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer;
import de.tudarmstadt.ukp.clarin.webanno.project.initializers.TokenLayerInitializer;
import de.tudarmstadt.ukp.inception.annotation.feature.string.StringFeatureSupport;
import de.tudarmstadt.ukp.inception.project.api.FeatureInitializer;
import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.config.InceptionBasicProjectInitializersAutoConfiguration;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings;

/**
* <p>
* This class is exposed as a Spring Component via
* {@link InceptionBasicProjectInitializersAutoConfiguration#commentFeatureInitializer}.
* </p>
*/
@Order(10)
public class CommentFeatureInitializer
implements FeatureInitializer
{
private static final PackageResourceReference THUMBNAIL = new PackageResourceReference(
MethodHandles.lookup().lookupClass(), "BasicSpanLayerInitializer.svg");

public static final String BASIC_COMMENT_FEATURE_NAME = "comment";

private final AnnotationSchemaService annotationSchemaService;
private final StringFeatureSupport stringFeatureSupport;

@Autowired
public CommentFeatureInitializer(AnnotationSchemaService aAnnotationSchemaService,
StringFeatureSupport aStringFeatureSupport)
{
annotationSchemaService = aAnnotationSchemaService;
stringFeatureSupport = aStringFeatureSupport;
}

@Override
public String getName()
{
return "Comment feature";
}

@Override
public Optional<String> getDescription()
{
return Optional.of(Strings.getString("comment-feature.description"));
}

@Override
public Optional<ResourceReference> getThumbnail()
{
return Optional.of(THUMBNAIL);
}

@Override
public boolean alreadyApplied(AnnotationLayer aLayer)
{
if (aLayer.isBuiltIn()) {
return true;
}

return annotationSchemaService.existsFeature(BASIC_COMMENT_FEATURE_NAME, aLayer);
}

@Override
public List<Class<? extends ProjectInitializer>> getDependencies()
{
return asList(
// Because locks to token boundaries
TokenLayerInitializer.class, //
// Tagsets
BasicSpanTagSetInitializer.class);
}

@Override
public void configure(AnnotationLayer aLayer) throws IOException
{
var commentFeature = AnnotationFeature.builder() //
.withName(BASIC_COMMENT_FEATURE_NAME) //
.withUiName("Comment") //
.withDescription("Leave comments here.") //
.withType(TYPE_NAME_STRING) //
.withLayer(aLayer) //
.withCuratable(false) //
.withVisible(false) //
.withIncludeInHover(true) //
.build();

var traits = stringFeatureSupport.createDefaultTraits();
traits.setMultipleRows(true);
traits.setDynamicSize(true);
stringFeatureSupport.writeTraits(commentFeature, traits);

annotationSchemaService.createFeature(commentFeature);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import de.tudarmstadt.ukp.inception.annotation.feature.string.StringFeatureSupport;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicProjectInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicRelationLayerInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicRelationRecommenderInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicRelationTagSetInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicSpanLayerInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicSpanRecommenderInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.BasicSpanTagSetInitializer;
import de.tudarmstadt.ukp.inception.project.initializers.basic.CommentFeatureInitializer;
import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService;
import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration;
import de.tudarmstadt.ukp.inception.recommendation.imls.stringmatch.config.StringMatchingRecommenderAutoConfiguration;
Expand Down Expand Up @@ -70,6 +72,14 @@ public BasicSpanLayerInitializer basicSpanLayerInitializer(
return new BasicSpanLayerInitializer(aAnnotationSchemaService);
}

@Bean
public CommentFeatureInitializer commentFeatureInitializer(
AnnotationSchemaService aAnnotationSchemaService,
StringFeatureSupport aStringFeatureSupport)
{
return new CommentFeatureInitializer(aAnnotationSchemaService, aStringFeatureSupport);
}

@ConditionalOnBean({ RecommendationService.class, StringMatchingRecommenderFactory.class })
@Bean
public BasicSpanRecommenderInitializer basicSpanRecommenderInitializer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ basic-span-layer.description=Identify spans and classify them using labels. This
basic-relation-layer.description=Identify relations between basic span annotations and classify them using labels. This \
is a good starting point for any annotation task involving relations. You can define new relation labels as you go. \
Adding this layer will also add the basic span layer if it is not present in the project yet.

comment-feature.description=For annotators to leave comments.
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
Expand All @@ -92,6 +90,7 @@
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties;
import de.tudarmstadt.ukp.inception.project.api.FeatureInitializer;
import de.tudarmstadt.ukp.inception.project.api.ProjectInitializationRequest;
import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer;
import de.tudarmstadt.ukp.inception.project.api.ProjectService;
Expand Down Expand Up @@ -120,22 +119,24 @@ public class ProjectServiceImpl
private final UserDao userRepository;
private final ApplicationEventPublisher applicationEventPublisher;
private final RepositoryProperties repositoryProperties;
private final List<ProjectInitializer> initializerProxy;
private final List<ProjectInitializer> projectInitializerProxy;
private final List<FeatureInitializer> featureInitializerProxy;

private List<ProjectInitializer> initializers;
private List<ProjectInitializer> projectInitializers;
private List<FeatureInitializer> featureInitializers;

@Autowired
public ProjectServiceImpl(UserDao aUserRepository,
ApplicationEventPublisher aApplicationEventPublisher,
RepositoryProperties aRepositoryProperties,
@Lazy @Autowired(required = false) List<ProjectInitializer> aInitializerProxy,
EntityManager aEntityManager)
List<ProjectInitializer> aProjectInitializerProxy,
List<FeatureInitializer> aFeatureInitializerProxy, EntityManager aEntityManager)
{
entityManager = aEntityManager;
userRepository = aUserRepository;
applicationEventPublisher = aApplicationEventPublisher;
repositoryProperties = aRepositoryProperties;
initializerProxy = aInitializerProxy;
projectInitializerProxy = aProjectInitializerProxy;
featureInitializerProxy = aFeatureInitializerProxy;
}

@Override
Expand Down Expand Up @@ -806,14 +807,14 @@ public void onContextRefreshedEvent(ContextRefreshedEvent aEvent)

/* package private */ void init()
{
List<ProjectInitializer> inits = new ArrayList<>();
var projectInits = new ArrayList<ProjectInitializer>();

if (initializerProxy != null) {
inits.addAll(initializerProxy);
AnnotationAwareOrderComparator.sort(inits);
if (projectInitializerProxy != null) {
projectInits.addAll(projectInitializerProxy);
AnnotationAwareOrderComparator.sort(projectInits);

Set<Class<? extends ProjectInitializer>> initializerClasses = new HashSet<>();
for (ProjectInitializer init : inits) {
var initializerClasses = new HashSet<Class<? extends ProjectInitializer>>();
for (var init : projectInits) {
if (initializerClasses.add(init.getClass())) {
LOG.debug("Found project initializer: {}",
ClassUtils.getAbbreviatedName(init.getClass(), 20));
Expand All @@ -826,9 +827,33 @@ public void onContextRefreshedEvent(ContextRefreshedEvent aEvent)
}
}

BaseLoggers.BOOT_LOG.info("Found [{}] project initializers", inits.size());
BaseLoggers.BOOT_LOG.info("Found [{}] project initializers", projectInits.size());

initializers = unmodifiableList(inits);
projectInitializers = unmodifiableList(projectInits);

var featureInits = new ArrayList<FeatureInitializer>();

if (featureInitializerProxy != null) {
featureInits.addAll(featureInitializerProxy);
AnnotationAwareOrderComparator.sort(featureInits);

var initializerClasses = new HashSet<Class<? extends FeatureInitializer>>();
for (var init : featureInits) {
if (initializerClasses.add(init.getClass())) {
LOG.debug("Found feature initializer: {}",
ClassUtils.getAbbreviatedName(init.getClass(), 20));
}
else {
throw new IllegalStateException("There cannot be more than once instance "
+ "of each feature initializer class! Duplicate instance of class: "
+ init.getClass());
}
}
}

BaseLoggers.BOOT_LOG.info("Found [{}] feature initializers", projectInits.size());

featureInitializers = unmodifiableList(featureInits);
}

private void addMissingProjectSlugs()
Expand Down Expand Up @@ -862,21 +887,27 @@ private String generateRandomSlug()
@Override
public List<ProjectInitializer> listProjectInitializers()
{
return initializers;
return projectInitializers;
}

@Override
public List<FeatureInitializer> listFeatureInitializers()
{
return featureInitializers;
}

@Override
@Transactional
public void initializeProject(ProjectInitializationRequest aRequest) throws IOException
{
initializeProject(aRequest, initializers.stream() //
initializeProject(aRequest, projectInitializers.stream() //
.filter(ProjectInitializer::applyByDefault) //
.collect(Collectors.toList()));
}

private ProjectInitializer findProjectInitializer(Class<? extends ProjectInitializer> aType)
{
return initializers.stream().filter(i -> aType.isAssignableFrom(i.getClass())) //
return projectInitializers.stream().filter(i -> aType.isAssignableFrom(i.getClass())) //
.findFirst() //
.orElseThrow(() -> new IllegalArgumentException(
"No initializer of type [" + aType + "] exists!"));
Expand Down Expand Up @@ -912,7 +943,7 @@ public void initializeProject(ProjectInitializationRequest aRequest,
{
var allInits = new HashSet<Class<? extends ProjectInitializer>>();
var applied = new HashSet<Class<? extends ProjectInitializer>>();
for (var initializer : initializers) {
for (var initializer : projectInitializers) {
allInits.add(initializer.getClass());
if (initializer.alreadyApplied(aRequest.getProject())) {
applied.add(initializer.getClass());
Expand Down
Loading

0 comments on commit 3ee308e

Please sign in to comment.