Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide RO-Crate export for project summary #856

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Empty file.
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions docs/fair/research-objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# RO-crates support in the data manager app

[RO-crates](https://www.researchobject.org/ro-crate/) (RO: Research Object) provide a way structured and machine-actionable way to bundle metadata and files in one single digital object. We will
look into how RO-Crates can be supported in the data manager.

## RO-Crate structure

The latest version up to the writing of this document is [RO-Crate 1.2-DRAFT](https://www.researchobject.org/ro-crate/specification/1.2-DRAFT/index.html). The last stable release is [RO-Crate 1.1](https://www.researchobject.org/ro-crate/specification/1.1/index.html).


## Use case: project information

Project information such as title, identifier and contact person(s) are very basic and high-level metadata often
used throughout a research project life cycle. This information is frequently consumed by humans (e.g. project managers, principal investigators, funding agencies) as well as machines to process or re-use it (e.g. in computational workflows).

How can this metadata be provided in a FAIR compliant manner and serving both: humans and machines optimally.

### Suggested RO-Crate implementation

Possible structure for an RO-Crate containing QBiC project information:

![Example project information RO-Crate](project-information-example-ro-crate.png)

This simple RO-Crate consists of only three files:

- _ro-crate-metadata.json_: part of the RO-Crate specification, this file describes the content of the RO-crate object. You will see this file in every RO-Crate.
- _project-information.yml_: some machine-actionable high level metadata about a QBiC project, e.g. title, identifier, contact
- _project-information.docx_: some human-readable, visually appealing information about a QBiC project

1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>

</dependencies>

</dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
flex: 1 1;
}

.button-bar {
display: inline-flex;
column-gap: var(--lumo-space-s);
}

.navigation-button .button {
box-shadow: var(--lumo-box-shadow-xs);
}
Expand Down
55 changes: 55 additions & 0 deletions user-interface/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,61 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- docx4j dependencies !-->
<!-- https://mvnrepository.com/artifact/org.docx4j/docx4j-core -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-core</artifactId>
<version>11.5.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Needed for docx4j !-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>11.4.11</version>
</dependency>


<!-- Needed for building RO-Crates -->
<dependency>
<groupId>edu.kit.datamanager</groupId>
<artifactId>ro-crate-java</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Binary file modified user-interface/src/main/bundles/dev.bundle
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package life.qbic.datamanager.export;

import java.io.File;
import life.qbic.datamanager.export.model.ResearchProject;

/**
* <b>File format supplier</b>
* <p>
* Interface describing the API of suppliers for different file formats.
*
* @since 1.6.0
*/
public interface FileFormatSupplier {

File from(String fileName, ResearchProject researchProject) throws FormatException;

class FormatException extends RuntimeException {

public FormatException(String message) {
}

public FormatException(String message, Throwable cause) {
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package life.qbic.datamanager.export;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* <b>Temporary Director</b>
* <p>
* Provides a temporary directory for the application to use when files need to be created for
* exporting them.
*
* @since <version tag>
*/
@Component
public class TempDirectory {
KochTobi marked this conversation as resolved.
Show resolved Hide resolved

private final Path tempDir;

public TempDirectory(@Value("${service.host.temp.dir}") String tempDirectoryPath)
throws IOException {
Path tempDirPath = Path.of(tempDirectoryPath);
if (!Files.exists(tempDirPath)) {
throw new IOException(tempDirectoryPath + " does not exist");
}
if (!Files.isWritable(tempDirPath)) {
throw new IOException(tempDirectoryPath + " is not writable");
}
if (!Files.isExecutable(tempDirPath)) {
throw new IOException(tempDirectoryPath + " is not executable");
}
tempDir = tempDirPath;
}

/**
* Creates a new directory in the configured temporary directory location.
* <p>
* The method minimises the probability for collisions, so the client does not need to take care
* about potential existing directories.
*
* @return the path to the newly created directory in the app's global temporary directory
* @throws IOException in case the directory could not be created
* @since 1.6.0
*/
public Path createDirectory() throws IOException {
return Files.createDirectory(tempDir.resolve(UUID.randomUUID().toString()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package life.qbic.datamanager.export.docx;

import java.io.File;
import life.qbic.datamanager.export.FileFormatSupplier;
import life.qbic.datamanager.export.model.ContactPoint;
import life.qbic.datamanager.export.model.ResearchProject;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;

/**
* <b>DOCX formatter implementation</b>
* <p>
* Creates DOCX representations of various content types in a Data Manager's project.
*
* @since 1.0.0
*/
public class DocxSupplier implements FileFormatSupplier {

public static DocxSupplier create() {
return new DocxSupplier();
}

@Override
public File from(String fileName, ResearchProject researchProject) {
try {
var wordPackage = WordprocessingMLPackage.createPackage();
var mainDocument = wordPackage.getMainDocumentPart();
addTitle(mainDocument, researchProject);
addProjectId(mainDocument, researchProject);
addSection(mainDocument, "Description", researchProject.description());
addSectionTitle(mainDocument, "Contact Points");
researchProject.contactPoint()
.forEach(contactPoint -> addContactPoint(mainDocument, contactPoint));
File file = new File(fileName);
wordPackage.save(file);
return file;
} catch (Docx4JException e) {
throw new FormatException("Creating docx package failed. ", e);
}
}

private void addTitle(MainDocumentPart mainDocumentPart, ResearchProject researchProject) {
mainDocumentPart.addStyledParagraphOfText("Title", researchProject.name());
}

private void addProjectId(MainDocumentPart mainDocumentPart, ResearchProject researchProject) {
mainDocumentPart.addStyledParagraphOfText("Subtitle",
"Project ID: " + researchProject.identifier());
}

private void addSection(MainDocumentPart mainDocumentPart, String sectionTitle,
String sectionContent) {
mainDocumentPart.addStyledParagraphOfText("Heading1", sectionTitle);
mainDocumentPart.addParagraphOfText(sectionContent);
}

private void addSectionTitle(MainDocumentPart mainDocumentPart, String title) {
mainDocumentPart.addStyledParagraphOfText("Heading1", title);
}

private void addContactPoint(MainDocumentPart mainDocumentPart, ContactPoint contactPoint) {
var contactPointFormatted = "%s (%s) - %s".formatted(contactPoint.name(),
contactPoint.contactType(), contactPoint.email());
mainDocumentPart.addParagraphOfText(contactPointFormatted);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package life.qbic.datamanager.export.model;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* A schema.org <a href="https://schema.org/ContactPoint">ContactPoint</a> representation in Java.
*
* @since 1.6.0.
*/
public class ContactPoint {

@JsonProperty(value = "@type")
private final String type = "ContactPoint";

@JsonProperty(value = "name")
private String name;

@JsonProperty(value = "email")
private String email;

@JsonProperty(value = "contactType")
private String contactType;

public static ContactPoint from(String name, String email, String contactType) {
ContactPoint contactPoint = new ContactPoint();
contactPoint.name = name;
contactPoint.email = email;
contactPoint.contactType = contactType;
return contactPoint;
}

public String name() {
return name;
}

public String email() {
return email;
}

public String contactType() {
return contactType;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package life.qbic.datamanager.export.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

/**
* A schema.org <a href="https://schema.org/ResearchProject">ResearchProject</a> representation in Java.
*
* @since 1.6.0.
*/
public class ResearchProject {

@JsonProperty(value = "@type")
private final String type = "ResearchProject";

@JsonProperty(value = "name")
private String name;

@JsonProperty(value = "identifier")
private String identifier;

@JsonProperty(value = "description")
private String description;

@JsonProperty(value = "contactPoint")
private List<ContactPoint> contactPoint;

public static ResearchProject from(String name, String identifier, String description, List<ContactPoint> contactPoint) {
ResearchProject project = new ResearchProject();
project.name = name;
project.identifier = identifier;
project.description = description;
project.contactPoint = contactPoint.stream().toList();
return project;
}

public String name() {
return name;
}

public String identifier() {
return identifier;
}

public String description() {
return description;
}

public List<ContactPoint> contactPoint() {
return contactPoint.stream().toList();
}

}
Loading