Skip to content

Commit

Permalink
Merge branch 'main' into refactoring/3415-Move-all-configuration-opti…
Browse files Browse the repository at this point in the history
…ons-to-Spring-configuration-beans

* main:
  [maven-release-plugin] prepare for next development iteration
  [maven-release-plugin] prepare release inception-34.1
  #5089 - Document structure is not retained when preparing a document as curation target
  #5089 - Document structure is not retained when preparing a document as curation target
  #5087 - Interactive recommender sidebar does not invoke the right recommender
  #5085 - Error displaying document when there are no visible layers
  • Loading branch information
reckart committed Oct 15, 2024
2 parents c4e301b + 4f86d40 commit fc2fad4
Show file tree
Hide file tree
Showing 62 changed files with 717 additions and 409 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

import org.apache.uima.cas.CAS;

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

@FunctionalInterface
public interface CasStorageServiceAction
{
void apply(CAS aCas) throws Exception;
void apply(SourceDocument aDocument, String aDataOwner, CAS aCas) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
import de.tudarmstadt.ukp.clarin.webanno.api.casstorage.ConcurentCasModificationException;
import de.tudarmstadt.ukp.clarin.webanno.diag.CasDoctor;
import de.tudarmstadt.ukp.clarin.webanno.diag.CasDoctorException;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.inception.annotation.storage.config.CasStorageCacheProperties;
import de.tudarmstadt.ukp.inception.annotation.storage.config.CasStorageServiceAutoConfiguration;
Expand Down Expand Up @@ -679,34 +678,11 @@ public boolean deleteCas(SourceDocument aDocument, String aUsername)
}

@Override
public void analyzeAndRepair(SourceDocument aDocument, String aUsername, CAS aCas)
public void analyzeAndRepair(SourceDocument aDocument, String aDataOwner, CAS aCas)
{
analyzeAndRepair(aDocument.getProject(), aDocument.getName(), aDocument.getId(), aUsername,
aCas);
}
var project = aDocument.getProject();

/**
* Runs {@link CasDoctor} in repair mode on the given CAS (if repairs are active), otherwise it
* runs only in analysis mode.
* <p>
* <b>Note:</b> {@link CasDoctor} is an optional service. If no {@link CasDoctor} implementation
* is available, this method returns without doing anything.
*
* @param aProject
* the project
* @param aDocumentName
* the document name (used for logging)
* @param aDocumentId
* the aDocument ID (used for logging)
* @param aUsername
* the user owning the CAS (used for logging)
* @param aCas
* the CAS object
*/
private void analyzeAndRepair(Project aProject, String aDocumentName, long aDocumentId,
String aUsername, CAS aCas)
{
try (var logCtx = withProjectLogger(aProject)) {
try (var logCtx = withProjectLogger(project)) {
if (casDoctor == null) {
return;
}
Expand All @@ -715,18 +691,18 @@ private void analyzeAndRepair(Project aProject, String aDocumentName, long aDocu
// because the repairs do an analysis as a pre- and post-condition.
if (casDoctor.isRepairsActive()) {
try {
casDoctor.repair(aProject, aCas);
casDoctor.repair(aDocument, aDataOwner, aCas);
}
catch (Exception e) {
throw new DataRetrievalFailureException("Error repairing CAS of user ["
+ aUsername + "] for document [" + aDocumentName + "] (" + aDocumentId
+ ") in project[" + aProject.getName() + "] (" + aProject.getId() + ")",
throw new DataRetrievalFailureException(
"Error repairing CAS of user [" + aDataOwner + "] for document "
+ aDocument + " in project " + aDocument.getProject(),
e);
}
}
// If the repairs are not active, then we run the analysis explicitly
else {
analyze(aProject, aDocumentName, aDocumentId, aUsername, aCas);
analyze(aDocument, aDataOwner, aCas);
}
}
}
Expand All @@ -737,59 +713,55 @@ private void analyzeAndRepair(Project aProject, String aDocumentName, long aDocu
* <b>Note:</b> {@link CasDoctor} is an optional service. If no {@link CasDoctor} implementation
* is available, this method returns without doing anything.
*
* @param aProject
* the project
* @param aDocumentName
* the document name (used for logging)
* @param aDocumentId
* the aDocument ID (used for logging)
* @param aUsername
* @param aDocument
* the document
* @param aDataOwner
* the user owning the CAS (used for logging)
* @param aCas
* the CAS object
*/
private void analyze(Project aProject, String aDocumentName, long aDocumentId, String aUsername,
CAS aCas)
private void analyze(SourceDocument aDocument, String aDataOwner, CAS aCas)
{
if (casDoctor == null) {
return;
}

var project = aDocument.getProject();

try {
casDoctor.analyze(aProject, aCas);
casDoctor.analyze(aDocument, aDataOwner, aCas);
}
catch (CasDoctorException e) {
var detailMsg = new StringBuilder();
detailMsg.append("CAS Doctor found problems for user [").append(aUsername)
.append("] in document [").append(aDocumentName).append("] (")
.append(aDocumentId).append(") in project [").append(aProject.getName())
.append("] (").append(aProject.getId()).append(")\n");
detailMsg.append("CAS Doctor found problems for user [").append(aDataOwner)
.append("] in document ").append(aDocument).append(" in project ")
.append(project).append("\n");
e.getDetails().forEach(
m -> detailMsg.append(String.format("- [%s] %s%n", m.level, m.message)));

throw new DataRetrievalFailureException(detailMsg.toString());
}
catch (Exception e) {
throw new DataRetrievalFailureException("Error analyzing CAS of user [" + aUsername
+ "] in document [" + aDocumentName + "] (" + aDocumentId + ") in project["
+ aProject.getName() + "] (" + aProject.getId() + ")", e);
throw new DataRetrievalFailureException("Error analyzing CAS of user [" + aDataOwner
+ "] in document " + aDocument + " in project " + project, e);
}
}

@Override
public void exportCas(SourceDocument aDocument, String aUser, OutputStream aStream)
public void exportCas(SourceDocument aDocument, String aDataOwner, OutputStream aStream)
throws IOException
{
// Ensure that the CAS is not being re-written and temporarily unavailable while we export
// it, then add this info to a mini-session to ensure that write-access is known
try (var session = CasStorageSession.openNested(true)) {
try (var access = new WithExclusiveAccess(aDocument, aUser)) {
session.add(aDocument.getId(), aUser, EXCLUSIVE_WRITE_ACCESS, access.getHolder());
try (var access = new WithExclusiveAccess(aDocument, aDataOwner)) {
session.add(aDocument.getId(), aDataOwner, EXCLUSIVE_WRITE_ACCESS,
access.getHolder());

driver.exportCas(aDocument, aUser, aStream);
driver.exportCas(aDocument, aDataOwner, aStream);
}
finally {
session.remove(aDocument.getId(), aUser);
session.remove(aDocument.getId(), aDataOwner);
}
}
catch (IOException e) {
Expand All @@ -801,19 +773,20 @@ public void exportCas(SourceDocument aDocument, String aUser, OutputStream aStre
}

@Override
public void importCas(SourceDocument aDocument, String aUser, InputStream aStream)
public void importCas(SourceDocument aDocument, String aDataOwner, InputStream aStream)
throws IOException
{
// Ensure that the CAS is not being re-written and temporarily unavailable while we export
// it, then add this info to a mini-session to ensure that write-access is known
try (var session = CasStorageSession.openNested(true)) {
try (var access = new WithExclusiveAccess(aDocument, aUser)) {
session.add(aDocument.getId(), aUser, EXCLUSIVE_WRITE_ACCESS, access.getHolder());
try (var access = new WithExclusiveAccess(aDocument, aDataOwner)) {
session.add(aDocument.getId(), aDataOwner, EXCLUSIVE_WRITE_ACCESS,
access.getHolder());

driver.importCas(aDocument, aUser, aStream);
driver.importCas(aDocument, aDataOwner, aStream);
}
finally {
session.remove(aDocument.getId(), aUser);
session.remove(aDocument.getId(), aDataOwner);
}
}
catch (IOException e) {
Expand All @@ -825,39 +798,40 @@ public void importCas(SourceDocument aDocument, String aUser, InputStream aStrea
}

@Override
public void upgradeCas(SourceDocument aDocument, String aUser) throws IOException
public void upgradeCas(SourceDocument aDocument, String aDataOwner) throws IOException
{
Validate.notNull(aDocument, "Source document must be specified");
Validate.notBlank(aUser, "User must be specified");
Validate.notBlank(aDataOwner, "Data owner must be specified");

forceActionOnCas(aDocument, aUser, //
forceActionOnCas(aDocument, aDataOwner, //
(doc, user) -> driver.readCas(doc, user),
(cas) -> schemaService.upgradeCas(cas, aDocument, aUser), //
(doc, user, cas) -> schemaService.upgradeCas(cas, doc, user), //
true);
}

@Override
public void forceActionOnCas(SourceDocument aDocument, String aUser,
public void forceActionOnCas(SourceDocument aDocument, String aDataOwner,
CasStorageServiceLoader aLoader, CasStorageServiceAction aAction, boolean aSave)
throws IOException
{
// Ensure that the CAS is not being re-written and temporarily unavailable while we check
// upgrade it, then add this info to a mini-session to ensure that write-access is known
try (var session = CasStorageSession.openNested(true)) {
try (var access = new WithExclusiveAccess(aDocument, aUser)) {
session.add(aDocument.getId(), aUser, EXCLUSIVE_WRITE_ACCESS, access.getHolder());
try (var access = new WithExclusiveAccess(aDocument, aDataOwner)) {
session.add(aDocument.getId(), aDataOwner, EXCLUSIVE_WRITE_ACCESS,
access.getHolder());

var cas = aLoader.load(aDocument, aUser);
var cas = aLoader.load(aDocument, aDataOwner);
access.setCas(cas);

aAction.apply(cas);
aAction.apply(aDocument, aDataOwner, cas);

if (aSave) {
realWriteCas(aDocument, aUser, cas);
realWriteCas(aDocument, aDataOwner, cas);
}
}
finally {
session.remove(aDocument.getId(), aUser);
session.remove(aDocument.getId(), aDataOwner);
}
}
catch (IOException e) {
Expand Down Expand Up @@ -1118,7 +1092,7 @@ public void beforeLayerConfigurationChanged(LayerConfigurationChangedEvent aEven
private void realWriteCas(SourceDocument aDocument, String aUserName, CAS aCas)
throws IOException
{
analyze(aDocument.getProject(), aDocument.getName(), aDocument.getId(), aUserName, aCas);
analyze(aDocument, aUserName, aCas);

if (CasStorageSession.exists()) {
var session = CasStorageSession.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void render(VDocument aResponse, RenderRequest aRequest)

Validate.notNull(cas, "CAS cannot be null");

if (aRequest.getVisibleLayers().isEmpty() || isEmpty(documentText)) {
if (isEmpty(documentText)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static de.tudarmstadt.ukp.clarin.webanno.brat.schema.BratSchemaGeneratorImpl.getBratTypeName;
import static de.tudarmstadt.ukp.clarin.webanno.model.ScriptDirection.RTL;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.uima.cas.text.AnnotationPredicates.covering;
import static org.apache.uima.cas.text.AnnotationPredicates.overlappingAtBegin;
Expand Down Expand Up @@ -251,11 +252,11 @@ private static List<Argument> getArgument(VID aGovernorFs, VID aDependentFs)

private void renderText(VDocument aVDoc, GetDocumentResponse aResponse, RenderRequest aRequest)
{
if (!aRequest.isIncludeText()) {
if (!aRequest.isIncludeText() || isEmpty(aVDoc.getText())) {
return;
}

String visibleText = aVDoc.getText();
var visibleText = aVDoc.getText();
char replacementChar = 0;
if (StringUtils.isNotEmpty(properties.getWhiteSpaceReplacementCharacter())) {
replacementChar = properties.getWhiteSpaceReplacementCharacter().charAt(0);
Expand All @@ -267,9 +268,13 @@ private void renderText(VDocument aVDoc, GetDocumentResponse aResponse, RenderRe

private void renderBratTokensFromText(GetDocumentResponse aResponse, VDocument aVDoc)
{
List<Offsets> bratTokenOffsets = new ArrayList<>();
String visibleText = aVDoc.getText();
BreakIterator bi = BreakIterator.getWordInstance(Locale.ROOT);
if (isEmpty(aVDoc.getText())) {
return;
}

var bratTokenOffsets = new ArrayList<Offsets>();
var visibleText = aVDoc.getText();
var bi = BreakIterator.getWordInstance(Locale.ROOT);
bi.setText(visibleText);
int last = bi.first();
int cur = bi.next();
Expand Down
4 changes: 4 additions & 0 deletions inception/inception-curation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-schema-api</artifactId>
</dependency>
<dependency>
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-io-xml</artifactId>
</dependency>
<dependency>
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-model</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import de.tudarmstadt.ukp.inception.annotation.storage.CasMetadataUtils;
import de.tudarmstadt.ukp.inception.curation.merge.strategy.DefaultMergeStrategy;
import de.tudarmstadt.ukp.inception.curation.merge.strategy.MergeStrategy;
import de.tudarmstadt.ukp.inception.io.xml.dkprocore.XmlNodeUtils;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;
import de.tudarmstadt.ukp.inception.schema.api.adapter.IllegalFeatureValueException;
Expand Down Expand Up @@ -412,25 +413,26 @@ private void clearAnnotations(SourceDocument aDocument, CAS aCas) throws UIMAExc
aCas.setDocumentText(backup.getDocumentText());

transferSegmentation(aDocument.getProject(), aCas, backup);
XmlNodeUtils.transferXmlDocumentStructure(aCas, backup);
}

/**
* If tokens and/or sentences are not editable, then they are not part of the curation process
* and we transfer them from the template CAS.
*/
private void transferSegmentation(Project aProject, CAS aCas, CAS backup)
private void transferSegmentation(Project aProject, CAS aTarget, CAS aSource)
{
if (!schemaService.isTokenLayerEditable(aProject)) {
// Transfer token boundaries
for (var t : selectTokens(backup)) {
aCas.addFsToIndexes(createToken(aCas, t.getBegin(), t.getEnd()));
for (var t : selectTokens(aSource)) {
aTarget.addFsToIndexes(createToken(aTarget, t.getBegin(), t.getEnd()));
}
}

if (!schemaService.isSentenceLayerEditable(aProject)) {
// Transfer sentence boundaries
for (var s : selectSentences(backup)) {
aCas.addFsToIndexes(createSentence(aCas, s.getBegin(), s.getEnd()));
for (var s : selectSentences(aSource)) {
aTarget.addFsToIndexes(createSentence(aTarget, s.getBegin(), s.getEnd()));
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions inception/inception-diag/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-annotation-storage-api</artifactId>
</dependency>
<dependency>
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-documents-api</artifactId>
</dependency>
<dependency>
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-model</artifactId>
Expand All @@ -64,6 +68,10 @@
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-support</artifactId>
</dependency>
<dependency>
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-io-xml</artifactId>
</dependency>
<dependency>
<groupId>de.tudarmstadt.ukp.inception.app</groupId>
<artifactId>inception-api-annotation</artifactId>
Expand Down
Loading

0 comments on commit fc2fad4

Please sign in to comment.