From 87afe6ba615c017b8de5f9ed23485cbb33d7c82d Mon Sep 17 00:00:00 2001 From: fmauz Date: Mon, 4 Sep 2023 13:40:11 +0200 Subject: [PATCH 01/28] remove unused service --- .../lbac/search/termvector/TermVectorEntityService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java b/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java index 275416100..25957247d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java @@ -24,7 +24,6 @@ import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.file.StemmedWordOrigin; import de.ipb_halle.lbac.message.TermVectorMessage; -import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.search.document.TermOcurrence; import java.io.Serializable; @@ -54,8 +53,6 @@ public class TermVectorEntityService implements Serializable { private Logger logger; - @Inject - private CollectionService collectionService; @Inject private FileEntityService fileEntityService; From c06a53c2e8472a76f1689b039121e9caa17cd199 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Tue, 5 Sep 2023 09:41:40 +0200 Subject: [PATCH 02/28] Separate Text Extraction from UI --- crimsy-api/pom.xml | 114 ++++++++++++++++ .../ipb_halle/crimsy_api}/AttributeTag.java | 2 +- .../ipb_halle/crimsy_api}/AttributeType.java | 2 +- .../java/de/ipb_halle/crimsy_api}/DTO.java | 2 +- pom.xml | 2 + tx-api/pom.xml | 129 ++++++++++++++++++ .../ipb_halle/tx/file}/AttachmentHolder.java | 2 +- .../de/ipb_halle/tx}/file/FileObject.java | 43 +++--- .../ipb_halle/tx}/file/FileObjectEntity.java | 30 ++-- .../de/ipb_halle/tx/file/FileObjectList.java | 14 +- .../ipb_halle/tx}/file/StemmedWordOrigin.java | 2 +- .../de/ipb_halle/tx}/file/TermFrequency.java | 2 +- .../ipb_halle/tx}/file/TermFrequencyList.java | 4 +- .../de/ipb_halle/tx}/file/TermVector.java | 4 +- .../ipb_halle/tx}/file/TermVectorEntity.java | 2 +- .../de/ipb_halle/tx}/file/TermVectorId.java | 6 +- .../file/UnsupportedLanguageException.java | 2 +- tx/pom.xml | 9 +- ui/pom.xml | 15 +- .../de/ipb_halle/lbac/admission/ACEntry.java | 2 +- .../lbac/admission/ACEntryEntity.java | 4 +- .../ipb_halle/lbac/admission/ACEntryId.java | 4 +- .../de/ipb_halle/lbac/admission/ACList.java | 2 +- .../lbac/admission/ACListService.java | 2 +- .../lbac/admission/ACObjectEntity.java | 4 +- .../de/ipb_halle/lbac/admission/Group.java | 2 +- .../de/ipb_halle/lbac/admission/Member.java | 2 +- .../lbac/admission/MemberEntity.java | 4 +- .../ipb_halle/lbac/admission/Membership.java | 2 +- .../lbac/admission/MembershipEntity.java | 4 +- .../ipb_halle/lbac/admission/NestingPath.java | 2 +- .../de/ipb_halle/lbac/admission/User.java | 2 +- .../lbac/collections/Collection.java | 4 +- .../lbac/collections/CollectionEntity.java | 4 +- .../CollectionEntityGraphBuilder.java | 2 +- .../CollectionSearchConditionBuilder.java | 2 +- .../ipb_halle/lbac/container/Container.java | 2 +- .../lbac/container/ContainerNesting.java | 2 +- .../lbac/container/ContainerType.java | 2 +- .../container/entity/ContainerEntity.java | 4 +- .../ipb_halle/lbac/datalink/LinkedData.java | 2 +- .../de/ipb_halle/lbac/device/job/Job.java | 2 +- .../de/ipb_halle/lbac/device/print/Label.java | 2 +- .../ipb_halle/lbac/device/print/Printer.java | 2 +- .../java/de/ipb_halle/lbac/entity/Cloud.java | 1 + .../de/ipb_halle/lbac/entity/CloudNode.java | 1 + .../de/ipb_halle/lbac/entity/InfoObject.java | 1 + .../java/de/ipb_halle/lbac/entity/Node.java | 1 + .../de/ipb_halle/lbac/entity/NodeEntity.java | 4 +- .../java/de/ipb_halle/lbac/exp/ExpRecord.java | 2 +- .../de/ipb_halle/lbac/exp/Experiment.java | 2 +- .../ipb_halle/lbac/exp/ExperimentEntity.java | 4 +- .../de/ipb_halle/lbac/exp/assay/Assay.java | 2 +- .../ipb_halle/lbac/exp/assay/AssayEntity.java | 4 +- .../search/ExperimentEntityGraphBuilder.java | 2 +- .../ExperimentSearchConditionBuilder.java | 2 +- .../java/de/ipb_halle/lbac/exp/text/Text.java | 2 +- .../ipb_halle/lbac/exp/text/TextEntity.java | 4 +- .../lbac/exp/virtual/NullRecord.java | 2 +- .../ipb_halle/lbac/file/FileDeleteExec.java | 22 +-- .../lbac/file/FileDeleteWebService.java | 2 +- .../lbac/file/FileEntityService.java | 20 ++- .../lbac/file/FileSearchRequest.java | 2 +- .../de/ipb_halle/lbac/file/UploadToCol.java | 4 +- .../lbac/file/save/FileAnalyser.java | 4 +- .../ipb_halle/lbac/file/save/FileSaver.java | 7 +- .../java/de/ipb_halle/lbac/forum/Posting.java | 2 +- .../java/de/ipb_halle/lbac/forum/Topic.java | 2 +- .../java/de/ipb_halle/lbac/items/Item.java | 2 +- .../de/ipb_halle/lbac/items/ItemHistory.java | 2 +- .../lbac/items/ItemPositionsHistory.java | 2 +- .../lbac/items/entity/ItemEntity.java | 4 +- .../lbac/items/entity/ItemPositionEntity.java | 4 +- .../search/ItemSearchConditionBuilder.java | 2 +- .../items/service/ItemEntityGraphBuilder.java | 2 +- .../lbac/items/service/ItemService.java | 2 +- .../de/ipb_halle/lbac/material/Material.java | 2 +- .../biomaterial/BioMaterialDifference.java | 2 +- .../material/biomaterial/TaxonomyLevel.java | 2 +- .../entity/MaterialDetailRightEntity.java | 4 +- .../common/entity/MaterialEntity.java | 4 +- .../index/MaterialIndexEntryEntity.java | 4 +- .../MaterialSearchConditionBuilder.java | 2 +- .../service/MaterialEntityGraphBuilder.java | 2 +- .../common/service/MaterialService.java | 2 +- .../composition/CompositionDifference.java | 2 +- .../material/sequence/SequenceEntity.java | 4 +- .../sequence/history/SequenceDifference.java | 2 +- .../SequenceSearchConditionBuilder.java | 2 +- .../search/service/SequenceSearchService.java | 2 +- .../material/structure/MoleculeEntity.java | 4 +- .../de/ipb_halle/lbac/project/Project.java | 2 +- .../ipb_halle/lbac/project/ProjectEntity.java | 4 +- .../project/ProjectEntityGraphBuilder.java | 2 +- .../ProjectSearchConditionBuilder.java | 2 +- .../lbac/reporting/report/Report.java | 2 +- .../lbac/search/EntityGraphBuilder.java | 2 +- .../lbac/search/SearchConditionBuilder.java | 2 +- .../lbac/search/document/Document.java | 4 +- .../document/DocumentEntityGraphBuilder.java | 6 +- .../DocumentSearchConditionBuilder.java | 2 +- .../document/DocumentSearchService.java | 20 +-- .../download/DocumentDownloadBean.java | 2 +- .../document/download/DocumentWebService.java | 8 +- .../ipb_halle/lbac/search/lang/Attribute.java | 1 + .../lbac/search/lang/AttributeTags.java | 1 + .../ipb_halle/lbac/search/lang/Condition.java | 1 + .../search/lang/ConditionValueFetcher.java | 1 + .../ipb_halle/lbac/search/lang/DbField.java | 2 + .../lbac/search/lang/EntityGraph.java | 2 + .../lbac/search/lang/SqlBuilder.java | 1 + .../search/lang/SqlParamTableBuilder.java | 1 + .../termvector/TermVectorEntityService.java | 8 +- .../lbac/search/wordcloud/WordCloudBean.java | 4 +- .../search/wordcloud/WordCloudWebService.java | 2 +- .../search/wordcloud/WordTermListMerger.java | 2 +- .../ipb_halle/lbac/util/pref/Preference.java | 2 +- .../mock/FileEntityServiceMock.java | 2 +- .../lbac/file/FileEntityServiceTest.java | 6 +- .../ipb_halle/lbac/file/UploadToColTest.java | 1 + .../lbac/file/mock/FileEntityServiceMock.java | 2 +- .../lbac/file/mock/UploadToColMock.java | 2 +- .../lbac/file/save/FileAnalyserTest.java | 4 +- .../lbac/file/save/FileSaverTest.java | 18 +-- .../SequenceSearchConditionBuilderTest.java | 2 +- .../download/DocumentDownloadBeanTest.java | 6 +- .../download/DocumentWebServiceTest.java | 6 +- .../lbac/search/lang/ConditionTest.java | 1 + .../lbac/search/lang/HorrorEntity.java | 2 + .../lbac/search/lang/SqlBuilderTest.java | 1 + .../search/lang/SqlParamTableBuilderTest.java | 1 + .../relevance/RelevanceCalculatorTest.java | 4 +- .../TermVectorEntityServiceTest.java | 10 +- .../wordcloud/WordTermListMergerTest.java | 2 +- .../mock/WordCloudWebServiceMock.java | 4 +- ui/src/test/resources/test-persistence.xml | 4 +- ui/web/WEB-INF/persistence.xml | 4 +- 137 files changed, 527 insertions(+), 252 deletions(-) create mode 100644 crimsy-api/pom.xml rename {ui/src/main/java/de/ipb_halle/lbac/search/lang => crimsy-api/src/main/java/de/ipb_halle/crimsy_api}/AttributeTag.java (96%) rename {ui/src/main/java/de/ipb_halle/lbac/search/lang => crimsy-api/src/main/java/de/ipb_halle/crimsy_api}/AttributeType.java (97%) rename {ui/src/main/java/de/ipb_halle/lbac/entity => crimsy-api/src/main/java/de/ipb_halle/crimsy_api}/DTO.java (95%) create mode 100644 tx-api/pom.xml rename {ui/src/main/java/de/ipb_halle/lbac/file/save => tx-api/src/main/java/de/ipb_halle/tx/file}/AttachmentHolder.java (95%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/FileObject.java (78%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/FileObjectEntity.java (81%) rename ui/src/main/java/de/ipb_halle/lbac/file/FileEntityList.java => tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java (75%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/StemmedWordOrigin.java (97%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/TermFrequency.java (97%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/TermFrequencyList.java (96%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/TermVector.java (97%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/TermVectorEntity.java (97%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/TermVectorId.java (93%) rename {ui/src/main/java/de/ipb_halle/lbac => tx-api/src/main/java/de/ipb_halle/tx}/file/UnsupportedLanguageException.java (96%) diff --git a/crimsy-api/pom.xml b/crimsy-api/pom.xml new file mode 100644 index 000000000..675b81fa3 --- /dev/null +++ b/crimsy-api/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + de.ipb-halle + crimsy-api + 1.0.0 + jar + + CRIMSy-API + http://github.com/ipb-halle/CRIMSy + + + UTF-8 + + + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + + + + + + + make-assembly + package + + single + + + + + + + + + + + + + junit + junit + 4.13.1 + test + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTag.java b/crimsy-api/src/main/java/de/ipb_halle/crimsy_api/AttributeTag.java similarity index 96% rename from ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTag.java rename to crimsy-api/src/main/java/de/ipb_halle/crimsy_api/AttributeTag.java index 995264538..593e3df2d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTag.java +++ b/crimsy-api/src/main/java/de/ipb_halle/crimsy_api/AttributeTag.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.search.lang; +package de.ipb_halle.crimsy_api; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeType.java b/crimsy-api/src/main/java/de/ipb_halle/crimsy_api/AttributeType.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeType.java rename to crimsy-api/src/main/java/de/ipb_halle/crimsy_api/AttributeType.java index 1711b893f..6f61dd8ff 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeType.java +++ b/crimsy-api/src/main/java/de/ipb_halle/crimsy_api/AttributeType.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.search.lang; +package de.ipb_halle.crimsy_api; /** * Attribute types for selection of search fields diff --git a/ui/src/main/java/de/ipb_halle/lbac/entity/DTO.java b/crimsy-api/src/main/java/de/ipb_halle/crimsy_api/DTO.java similarity index 95% rename from ui/src/main/java/de/ipb_halle/lbac/entity/DTO.java rename to crimsy-api/src/main/java/de/ipb_halle/crimsy_api/DTO.java index 76a6c4ff8..757d52bfc 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/entity/DTO.java +++ b/crimsy-api/src/main/java/de/ipb_halle/crimsy_api/DTO.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.entity; +package de.ipb_halle.crimsy_api; public interface DTO { diff --git a/pom.xml b/pom.xml index df646ba83..9dc9bafc8 100644 --- a/pom.xml +++ b/pom.xml @@ -30,9 +30,11 @@ pom + crimsy-api agency-api agency snowball + tx-api tx ui diff --git a/tx-api/pom.xml b/tx-api/pom.xml new file mode 100644 index 000000000..046da9094 --- /dev/null +++ b/tx-api/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + de.ipb-halle + tx-api + 1.0.1 + jar + + TX API + http://github.com/ipb-halle/CRIMSy + + + UTF-8 + + + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + + + + + + + make-assembly + package + + single + + + + + + + + + + + + + de.ipb-halle + crimsy-api + 1.0.0 + + + + + junit + junit + 4.13.1 + test + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/save/AttachmentHolder.java b/tx-api/src/main/java/de/ipb_halle/tx/file/AttachmentHolder.java similarity index 95% rename from ui/src/main/java/de/ipb_halle/lbac/file/save/AttachmentHolder.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/AttachmentHolder.java index 2f2f7c208..9b87957db 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/save/AttachmentHolder.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/AttachmentHolder.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file.save; +package de.ipb_halle.tx.file; /** * diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileObject.java b/tx-api/src/main/java/de/ipb_halle/tx/file/FileObject.java similarity index 78% rename from ui/src/main/java/de/ipb_halle/lbac/file/FileObject.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/FileObject.java index 9566c3e35..dc2a697f2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileObject.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/FileObject.java @@ -15,15 +15,12 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.sql.Timestamp; import java.util.Date; -import de.ipb_halle.lbac.file.save.AttachmentHolder; public class FileObject implements Serializable, DTO { @@ -33,8 +30,8 @@ public class FileObject implements Serializable, DTO { private String fileLocation; private String hash; private Date created; - private User user; - private AttachmentHolder collection; + private Integer userId; + private Integer collectionId; private String document_language; /** @@ -49,30 +46,28 @@ public FileObject() { * Constructor * * @param entity - * @param col - * @param u */ - public FileObject(FileObjectEntity entity, Collection col, User u) { + public FileObject(FileObjectEntity entity) { this.id = entity.getId(); this.name = entity.getName(); this.fileLocation = entity.getFilename(); this.hash = entity.getHash(); this.created = entity.getCreated(); this.document_language = entity.getDocument_language(); - this.collection = col; - this.user = u; + this.collectionId = entity.getCollectionId(); + this.userId = entity.getUserId(); } @Override public FileObjectEntity createEntity() { return new FileObjectEntity() - .setCollection(collection.getId()) + .setCollectionId(collectionId) .setCreatedFromDate(created) .setDocument_language(document_language) .setFilename(fileLocation) .setHash(hash) .setName(name) - .setUser(user.getId()) + .setUserId(userId) .setId(id); } @@ -123,22 +118,22 @@ public void setCreated(Timestamp created) { this.created = created; } - public User getUser() { - return user; + public Integer getCollectionId() { + return collectionId; } - public void setUser(User user) { - this.user = user; + public void setCollectionId(Integer colId) { + this.collectionId = colId; } - public AttachmentHolder getCollection() { - return collection; + public Integer getUserId() { + return userId; } - - public void setCollection(AttachmentHolder collection) { - this.collection = collection; + + public void setUserId(Integer uid) { + this.userId = uid; } - + public String getDocument_language() { return document_language; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileObjectEntity.java b/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectEntity.java similarity index 81% rename from ui/src/main/java/de/ipb_halle/lbac/file/FileObjectEntity.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectEntity.java index f9d8a5f8b..05dfe9525 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileObjectEntity.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectEntity.java @@ -15,12 +15,11 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import javax.persistence.*; -import javax.validation.constraints.Size; import java.io.Serializable; import java.sql.Timestamp; import java.util.Date; @@ -37,26 +36,23 @@ public class FileObjectEntity implements Serializable { private Integer id; @Column - @Size(min = 1, max = 255) private String name; @Column - @Size(min = 1, max = 255) private String filename; @Column - @Size(min = 1, max = 255) private String hash; @Column private Timestamp created; @Column(name = "user_id") - private Integer user; + private Integer userId; @AttributeTag(type = AttributeType.COLLECTION) @Column(name = "collection_id") - private Integer collection; + private Integer collectionId; @Column private String document_language; @@ -90,20 +86,20 @@ public Timestamp getCreated() { return created; } - public Integer getUser() { - return user; + public Integer getUserId() { + return userId; } - public Integer getCollection() { - return collection; + public Integer getCollectionId() { + return collectionId; } public String getDocument_language() { return document_language; } - public FileObjectEntity setCollection(Integer collection) { - this.collection = collection; + public FileObjectEntity setCollectionId(Integer colId) { + this.collectionId = colId; return this; } @@ -142,8 +138,8 @@ public FileObjectEntity setDocument_language(String document_language) { return this; } - public FileObjectEntity setUser(Integer user) { - this.user = user; + public FileObjectEntity setUserId(Integer uid) { + this.userId = uid; return this; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityList.java b/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java similarity index 75% rename from ui/src/main/java/de/ipb_halle/lbac/file/FileEntityList.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java index 3f4468e13..bd88da52b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityList.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; import java.io.Serializable; import java.util.List; @@ -26,17 +26,17 @@ * * @author fbroda */ -public class FileEntityList implements Serializable { +public class FileObjectList implements Serializable { private final static long serialVersionUID = 1L; - List fileEntities; + List fileObjects; - public List getFileEntities() { - return fileEntities; + public List getFileObjects() { + return fileObjects; } - public void setFileEntities(List fileEntities) { - this.fileEntities = fileEntities; + public void setFileObjects(List list) { + this.fileObjects = list; } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/StemmedWordOrigin.java b/tx-api/src/main/java/de/ipb_halle/tx/file/StemmedWordOrigin.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/file/StemmedWordOrigin.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/StemmedWordOrigin.java index 4131b6160..fdc7580f1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/StemmedWordOrigin.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/StemmedWordOrigin.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; import java.util.HashSet; import java.util.Set; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/TermFrequency.java b/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequency.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/file/TermFrequency.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequency.java index 14103247c..dfa06b789 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/TermFrequency.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequency.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; /** * Stores a stemmed word with its frequency in a corpus. diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/TermFrequencyList.java b/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequencyList.java similarity index 96% rename from ui/src/main/java/de/ipb_halle/lbac/file/TermFrequencyList.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequencyList.java index 004c5f07e..ee4ba4110 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/TermFrequencyList.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequencyList.java @@ -15,12 +15,11 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.Logger;import org.apache.logging.log4j.LogManager; /** * Represents a list of termvectors and its frequency with the representations @@ -31,7 +30,6 @@ public class TermFrequencyList { private List termFreq = new ArrayList<>(); - private Logger logger = LogManager.getLogger(TermFrequencyList.class); private List unstemmedWords = new ArrayList<>(); /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/TermVector.java b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVector.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/file/TermVector.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/TermVector.java index b58ba7777..a37fe4461 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/TermVector.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVector.java @@ -15,9 +15,9 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.util.Objects; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/TermVectorEntity.java b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorEntity.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/file/TermVectorEntity.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorEntity.java index 88a1349d6..0a447e277 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/TermVectorEntity.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorEntity.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; import java.io.Serializable; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/TermVectorId.java b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorId.java similarity index 93% rename from ui/src/main/java/de/ipb_halle/lbac/file/TermVectorId.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorId.java index 4ff4c8b3f..a4ce6f5cb 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/TermVectorId.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorId.java @@ -15,10 +15,10 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Embeddable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/UnsupportedLanguageException.java b/tx-api/src/main/java/de/ipb_halle/tx/file/UnsupportedLanguageException.java similarity index 96% rename from ui/src/main/java/de/ipb_halle/lbac/file/UnsupportedLanguageException.java rename to tx-api/src/main/java/de/ipb_halle/tx/file/UnsupportedLanguageException.java index ba3718990..c0fea9ac1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/UnsupportedLanguageException.java +++ b/tx-api/src/main/java/de/ipb_halle/tx/file/UnsupportedLanguageException.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.tx.file; /** * diff --git a/tx/pom.xml b/tx/pom.xml index 6bffd86f6..f2c14ba08 100644 --- a/tx/pom.xml +++ b/tx/pom.xml @@ -22,7 +22,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - de.ipb_halle + de.ipb-halle tx 1.0.1 jar @@ -96,6 +96,13 @@ + + + de.ipb-halle + tx-api + 1.0.1 + + commons-cli diff --git a/ui/pom.xml b/ui/pom.xml index 2bf9197e0..91c361e1c 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -600,7 +600,14 @@ - de.ipb_halle + de.ipb-halle + crimsy-api + 1.0.0 + + + + + de.ipb-halle tx 1.0.1 @@ -611,6 +618,12 @@ + + de.ipb-halle + tx-api + 1.0.1 + + org.xerial diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntry.java b/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntry.java index b313bb6d4..e0384548a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntry.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntry.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Obfuscatable; import java.io.Serializable; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryEntity.java b/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryEntity.java index f9bf8cd53..eab7f8602 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryId.java b/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryId.java index 3de696426..8ecdb0c50 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryId.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/ACEntryId.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Embeddable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/ACList.java b/ui/src/main/java/de/ipb_halle/lbac/admission/ACList.java index a8c41f1a2..4396cabc2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/ACList.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/ACList.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Obfuscatable; import java.io.Serializable; import java.util.HashMap; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/ACListService.java b/ui/src/main/java/de/ipb_halle/lbac/admission/ACListService.java index eaa50fb80..750fd1657 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/ACListService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/ACListService.java @@ -29,7 +29,7 @@ */ import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.search.lang.Attribute; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/ACObjectEntity.java b/ui/src/main/java/de/ipb_halle/lbac/admission/ACObjectEntity.java index 54fe2292a..cb61f0983 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/ACObjectEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/ACObjectEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import javax.persistence.Column; import javax.persistence.MappedSuperclass; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/Group.java b/ui/src/main/java/de/ipb_halle/lbac/admission/Group.java index 22ee6a8a7..02da80303 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/Group.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/Group.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Node; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/Member.java b/ui/src/main/java/de/ipb_halle/lbac/admission/Member.java index 8b7db94dd..6279cf5e0 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/Member.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/Member.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.entity.Obfuscatable; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/MemberEntity.java b/ui/src/main/java/de/ipb_halle/lbac/admission/MemberEntity.java index 83b0d257e..522b3a42f 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/MemberEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/MemberEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.util.Date; import java.util.UUID; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/Membership.java b/ui/src/main/java/de/ipb_halle/lbac/admission/Membership.java index 423c1ea32..abe4d88ec 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/Membership.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/Membership.java @@ -18,7 +18,7 @@ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/MembershipEntity.java b/ui/src/main/java/de/ipb_halle/lbac/admission/MembershipEntity.java index f0f26e9fa..e819bea5c 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/MembershipEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/MembershipEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/NestingPath.java b/ui/src/main/java/de/ipb_halle/lbac/admission/NestingPath.java index a88437bef..2af8e2237 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/NestingPath.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/NestingPath.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.util.HashSet; import java.util.Set; diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/User.java b/ui/src/main/java/de/ipb_halle/lbac/admission/User.java index de69d3792..c2e13d98a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/User.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/User.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.admission; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.entity.Obfuscatable; import de.ipb_halle.lbac.search.SearchTarget; diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java b/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java index f545affcb..35750f69e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java @@ -24,12 +24,12 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.entity.Obfuscatable; import java.io.Serializable; import java.nio.file.Paths; -import de.ipb_halle.lbac.file.save.AttachmentHolder; +import de.ipb_halle.tx.file.AttachmentHolder; public class Collection extends ACObject implements Serializable, Obfuscatable, DTO, AttachmentHolder { diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntity.java b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntity.java index 2f6e1a7f4..7e139c403 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntity.java @@ -22,8 +22,8 @@ * collection managment */ import de.ipb_halle.lbac.admission.ACObjectEntity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import javax.validation.constraints.Size; import java.io.Serializable; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntityGraphBuilder.java index c2a581aa9..80a4dc6c4 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionEntityGraphBuilder.java @@ -18,7 +18,7 @@ package de.ipb_halle.lbac.collections; import de.ipb_halle.lbac.search.EntityGraphBuilder; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionSearchConditionBuilder.java index e6f7b6a6c..bd93316f6 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionSearchConditionBuilder.java @@ -22,7 +22,7 @@ import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchConditionBuilder; import de.ipb_halle.lbac.search.SearchRequest; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/container/Container.java b/ui/src/main/java/de/ipb_halle/lbac/container/Container.java index 053ef14a4..8800f7f84 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/container/Container.java +++ b/ui/src/main/java/de/ipb_halle/lbac/container/Container.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.items.Item; import de.ipb_halle.lbac.container.entity.ContainerEntity; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.project.Project; import de.ipb_halle.lbac.search.SearchTarget; import de.ipb_halle.lbac.search.Searchable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/container/ContainerNesting.java b/ui/src/main/java/de/ipb_halle/lbac/container/ContainerNesting.java index bad28b8b0..234c7a118 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/container/ContainerNesting.java +++ b/ui/src/main/java/de/ipb_halle/lbac/container/ContainerNesting.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.container; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.container.entity.ContainerNestingEntity; import de.ipb_halle.lbac.container.entity.ContainerNestingId; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/container/ContainerType.java b/ui/src/main/java/de/ipb_halle/lbac/container/ContainerType.java index 2e544bc0b..d07764518 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/container/ContainerType.java +++ b/ui/src/main/java/de/ipb_halle/lbac/container/ContainerType.java @@ -18,7 +18,7 @@ package de.ipb_halle.lbac.container; import de.ipb_halle.lbac.container.entity.ContainerTypeEntity; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.util.Objects; diff --git a/ui/src/main/java/de/ipb_halle/lbac/container/entity/ContainerEntity.java b/ui/src/main/java/de/ipb_halle/lbac/container/entity/ContainerEntity.java index e54a74dba..9d8976f99 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/container/entity/ContainerEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/container/entity/ContainerEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.container.entity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/datalink/LinkedData.java b/ui/src/main/java/de/ipb_halle/lbac/datalink/LinkedData.java index 74a3eb1e3..db998dc93 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/datalink/LinkedData.java +++ b/ui/src/main/java/de/ipb_halle/lbac/datalink/LinkedData.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.datalink; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.exp.ExpRecord; import de.ipb_halle.lbac.exp.Payload; import de.ipb_halle.lbac.items.Item; diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java b/ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java index c03a82886..38e166e00 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.device.job; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.User; import java.util.Date; diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/Label.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/Label.java index 68fe44b36..3dc06a439 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/Label.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/Label.java @@ -20,7 +20,7 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.util.Date; diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/Printer.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/Printer.java index 6617e475e..528d932c6 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/Printer.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/Printer.java @@ -20,7 +20,7 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/entity/Cloud.java b/ui/src/main/java/de/ipb_halle/lbac/entity/Cloud.java index 0eddbc931..16e70b6e0 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/entity/Cloud.java +++ b/ui/src/main/java/de/ipb_halle/lbac/entity/Cloud.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.entity; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import javax.xml.bind.annotation.*; diff --git a/ui/src/main/java/de/ipb_halle/lbac/entity/CloudNode.java b/ui/src/main/java/de/ipb_halle/lbac/entity/CloudNode.java index e8c3a81fb..25feb5123 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/entity/CloudNode.java +++ b/ui/src/main/java/de/ipb_halle/lbac/entity/CloudNode.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.entity; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; import java.util.Date; diff --git a/ui/src/main/java/de/ipb_halle/lbac/entity/InfoObject.java b/ui/src/main/java/de/ipb_halle/lbac/entity/InfoObject.java index 029b91688..b4d17667d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/entity/InfoObject.java +++ b/ui/src/main/java/de/ipb_halle/lbac/entity/InfoObject.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.entity; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; import de.ipb_halle.lbac.admission.User; diff --git a/ui/src/main/java/de/ipb_halle/lbac/entity/Node.java b/ui/src/main/java/de/ipb_halle/lbac/entity/Node.java index 1b0b24511..e73e1c8a8 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/entity/Node.java +++ b/ui/src/main/java/de/ipb_halle/lbac/entity/Node.java @@ -23,6 +23,7 @@ * local or remote. (Serialized) node objects can be queried from the master * node to get information about all existing nodes. */ +import de.ipb_halle.crimsy_api.DTO; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; diff --git a/ui/src/main/java/de/ipb_halle/lbac/entity/NodeEntity.java b/ui/src/main/java/de/ipb_halle/lbac/entity/NodeEntity.java index aaa25c3d2..6ec5cda52 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/entity/NodeEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/entity/NodeEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.entity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; /** * Node This class represents a single node in the Bioactives Cloud. A node is diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/ExpRecord.java b/ui/src/main/java/de/ipb_halle/lbac/exp/ExpRecord.java index 14a2462a1..117194edb 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/ExpRecord.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/ExpRecord.java @@ -21,7 +21,7 @@ import de.ipb_halle.lbac.datalink.LinkedDataHolder; import de.ipb_halle.lbac.datalink.LinkedData; import de.ipb_halle.lbac.datalink.LinkText; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.util.ArrayList; import java.util.Date; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/Experiment.java b/ui/src/main/java/de/ipb_halle/lbac/exp/Experiment.java index a773348c7..ecbccdf7d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/Experiment.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/Experiment.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.project.Project; import de.ipb_halle.lbac.search.SearchTarget; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/ExperimentEntity.java b/ui/src/main/java/de/ipb_halle/lbac/exp/ExperimentEntity.java index 633856cf3..62efdfc5d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/ExperimentEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/ExperimentEntity.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.exp; import de.ipb_halle.lbac.admission.ACObjectEntity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import java.util.Date; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/assay/Assay.java b/ui/src/main/java/de/ipb_halle/lbac/exp/assay/Assay.java index 31d5a95da..e5cb0e739 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/assay/Assay.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/assay/Assay.java @@ -21,7 +21,7 @@ import de.ipb_halle.lbac.datalink.LinkedData; import com.corejsf.util.Messages; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.exp.ExpRecord; import de.ipb_halle.lbac.exp.ExpRecordType; import de.ipb_halle.lbac.material.Material; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/assay/AssayEntity.java b/ui/src/main/java/de/ipb_halle/lbac/exp/assay/AssayEntity.java index 7faa8b681..385b91af8 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/assay/AssayEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/assay/AssayEntity.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.exp.assay; import de.ipb_halle.lbac.datalink.LinkedDataType; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentEntityGraphBuilder.java index 695bb0d4e..20ea6801a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentEntityGraphBuilder.java @@ -26,7 +26,7 @@ import de.ipb_halle.lbac.items.service.ItemEntityGraphBuilder; import de.ipb_halle.lbac.material.common.service.MaterialEntityGraphBuilder; import de.ipb_halle.lbac.search.EntityGraphBuilder; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; import javax.persistence.criteria.JoinType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentSearchConditionBuilder.java index 7e6d0409d..41aafa890 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/search/ExperimentSearchConditionBuilder.java @@ -23,7 +23,7 @@ import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchConditionBuilder; import de.ipb_halle.lbac.search.SearchRequest; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/text/Text.java b/ui/src/main/java/de/ipb_halle/lbac/exp/text/Text.java index b1e02a39d..24151bbf5 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/text/Text.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/text/Text.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.exp.text; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.exp.ExpRecord; import de.ipb_halle.lbac.exp.ExpRecordType; import java.util.HashSet; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/text/TextEntity.java b/ui/src/main/java/de/ipb_halle/lbac/exp/text/TextEntity.java index 1ea9d3cf3..0ab53e407 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/text/TextEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/text/TextEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.exp.text; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/ui/src/main/java/de/ipb_halle/lbac/exp/virtual/NullRecord.java b/ui/src/main/java/de/ipb_halle/lbac/exp/virtual/NullRecord.java index 9c5cc522d..b758a623d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/exp/virtual/NullRecord.java +++ b/ui/src/main/java/de/ipb_halle/lbac/exp/virtual/NullRecord.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.exp.virtual; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.exp.ExpRecord; import de.ipb_halle.lbac.exp.ExpRecordType; import java.util.HashSet; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java index eaa5e4dad..d1ca7d32e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java @@ -19,6 +19,8 @@ import de.ipb_halle.lbac.service.FileService; +import de.ipb_halle.tx.file.FileObject; + import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; @@ -33,18 +35,18 @@ public class FileDeleteExec implements Runnable { private AsyncContext asyncContext; - private FileObject fileEntity; + private FileObject fileObject; private Logger logger; private FileEntityService fileEntityService; private FileService fs; - public FileDeleteExec(FileObject fe, FileEntityService fes, AsyncContext asyncContext) { + public FileDeleteExec(FileObject fo, FileEntityService fes, AsyncContext asyncContext) { this.logger = LogManager.getLogger(FileDeleteExec.class); this.asyncContext = asyncContext; - this.fileEntity = fe; + this.fileObject = fo; this.fileEntityService = fes; fs = new FileService(); @@ -65,21 +67,21 @@ public void run() { response.setContentType("text/html"); try { - fs.deleteFile(fileEntity.getFileLocation()); - this.logger.info(String.format("File %s in repository deleted.", fileEntity.getName())); + fs.deleteFile(fileObject.getFileLocation()); + this.logger.info(String.format("File %s in repository deleted.", fileObject.getName())); } catch (Exception e) { - this.logger.warn(String.format("Error deleting file %s in repository.", fileEntity.getName())); + this.logger.warn(String.format("Error deleting file %s in repository.", fileObject.getName())); this.logger.warn(e.getMessage()); } try { - fileEntityService.delete(fileEntity); - this.logger.info(String.format("file %s in db deleted.", fileEntity.getName())); + fileEntityService.delete(fileObject); + this.logger.info(String.format("file %s in db deleted.", fileObject.getName())); } catch (Exception e) { - this.logger.warn(String.format("Error deleting file entity %s in db.", fileEntity.getName())); + this.logger.warn(String.format("Error deleting file entity %s in db.", fileObject.getName())); this.logger.warn(e.getMessage()); } - this.logger.info(String.format("delete File %s complete.", fileEntity.getName())); + this.logger.info(String.format("delete File %s complete.", fileObject.getName())); response.setStatus(HttpServletResponse.SC_OK); } catch (Exception e) { diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java index ebec010e5..5f9abedde 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.CollectionService; - +import de.ipb_halle.tx.file.FileObject; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java index ab3130c32..4f0fe708a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java @@ -20,6 +20,9 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.admission.MemberService; +import de.ipb_halle.tx.file.FileObject; +import de.ipb_halle.tx.file.FileObjectEntity; +import de.ipb_halle.tx.file.TermVector; import java.io.Serializable; import java.math.BigInteger; import java.util.ArrayList; @@ -109,13 +112,13 @@ public List getAllFilesInCollection(Collection collection) { CriteriaQuery criteriaQuery = builder.createQuery(FileObjectEntity.class); Root fileRoot = criteriaQuery.from(FileObjectEntity.class); criteriaQuery.select(fileRoot); - criteriaQuery.where(builder.equal(fileRoot.get("collection"), collection.getId())); + criteriaQuery.where(builder.equal(fileRoot.get("collectionId"), collection.getId())); List entities = this.em.createQuery(criteriaQuery).getResultList(); List results = new ArrayList<>(); for (FileObjectEntity entity : entities) { - results.add(new FileObject(entity, collection, memberService.loadUserById(entity.getUser()))); + results.add(new FileObject(entity)); } return results; } @@ -147,10 +150,7 @@ public long getDocumentCount(Collection collection) { public FileObject getFileEntity(Integer id) { FileObjectEntity entity = this.em.find(FileObjectEntity.class, id); if (entity != null) { - return new FileObject( - entity, - collectionService.loadById(entity.getCollection()), - memberService.loadUserById(entity.getUser())); + return new FileObject(entity); } return null; } @@ -204,16 +204,12 @@ public List load(Map cmap) { predicates.add(builder.equal(fileObjectRoot.get("hash"), cmap.get("hash"))); } if (cmap.get("collection_id") != null) { - predicates.add(builder.equal(fileObjectRoot.get("collection"), cmap.get("collection_id"))); + predicates.add(builder.equal(fileObjectRoot.get("collectionId"), cmap.get("collection_id"))); } criteriaQuery.where(builder.and(predicates.toArray(new Predicate[]{}))); List results = new ArrayList<>(); for (FileObjectEntity entity : this.em.createQuery(criteriaQuery).getResultList()) { - results.add( - new FileObject( - entity, - collectionService.loadById(entity.getCollection()), - memberService.loadUserById(entity.getUser()))); + results.add(new FileObject(entity)); } return results; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java index 55c70b9a7..be915c873 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.file; -import de.ipb_halle.lbac.file.save.AttachmentHolder; +import de.ipb_halle.tx.file.AttachmentHolder; import de.ipb_halle.lbac.search.document.StemmedWordGroup; /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java index eeba54404..0b6f5b9a0 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java @@ -30,8 +30,10 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import de.ipb_halle.lbac.file.save.AttachmentHolder; +import de.ipb_halle.tx.file.AttachmentHolder; import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; +import de.ipb_halle.tx.file.StemmedWordOrigin; +import de.ipb_halle.tx.file.TermVector; import java.io.InputStream; import java.io.PrintWriter; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java index 60122f021..71418cc64 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.file.save; -import de.ipb_halle.lbac.file.StemmedWordOrigin; -import de.ipb_halle.lbac.file.TermVector; +import de.ipb_halle.tx.file.StemmedWordOrigin; +import de.ipb_halle.tx.file.TermVector; import de.ipb_halle.tx.text.LanguageDetectorFilter; import de.ipb_halle.tx.text.ParseTool; import de.ipb_halle.tx.text.TermVectorFilter; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java index 7b76497c2..1f1be4aae 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java @@ -19,8 +19,9 @@ import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; +import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.lbac.util.HexUtil; +import de.ipb_halle.tx.file.AttachmentHolder; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -71,9 +72,9 @@ protected Integer saveFileInDB(AttachmentHolder objectOfAttachedFile, String fil fileObject.setFileLocation("to be set"); fileObject.setName(fileName); fileObject.setCreated(new Date()); - fileObject.setUser(user); + fileObject.setUserId(user.getId()); fileObject.setHash(hash); - fileObject.setCollection(objectOfAttachedFile); + fileObject.setCollectionId(objectOfAttachedFile.getId()); fileObject = fileEntityService.save(fileObject); return fileObject.getId(); } diff --git a/ui/src/main/java/de/ipb_halle/lbac/forum/Posting.java b/ui/src/main/java/de/ipb_halle/lbac/forum/Posting.java index 7de306627..ef38de519 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/forum/Posting.java +++ b/ui/src/main/java/de/ipb_halle/lbac/forum/Posting.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.forum; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Obfuscatable; import de.ipb_halle.lbac.admission.User; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/forum/Topic.java b/ui/src/main/java/de/ipb_halle/lbac/forum/Topic.java index 9f817c76f..a33d8713e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/forum/Topic.java +++ b/ui/src/main/java/de/ipb_halle/lbac/forum/Topic.java @@ -20,7 +20,7 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.forum.topics.TopicCategory; import de.ipb_halle.lbac.admission.ACObject; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.entity.Obfuscatable; import de.ipb_halle.lbac.admission.User; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/Item.java b/ui/src/main/java/de/ipb_halle/lbac/items/Item.java index 14d108b8e..16347d819 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/Item.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/Item.java @@ -23,7 +23,7 @@ import de.ipb_halle.lbac.device.print.LabelType; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.items.entity.ItemEntity; import de.ipb_halle.lbac.material.Material; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/ItemHistory.java b/ui/src/main/java/de/ipb_halle/lbac/items/ItemHistory.java index b03d4df66..425d541af 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/ItemHistory.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/ItemHistory.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.container.Container; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.items.entity.ItemHistoryEntity; import de.ipb_halle.lbac.material.common.history.HistoryEntityId; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/ItemPositionsHistory.java b/ui/src/main/java/de/ipb_halle/lbac/items/ItemPositionsHistory.java index 18c29964d..1ad854b13 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/ItemPositionsHistory.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/ItemPositionsHistory.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.items; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity; import java.util.Date; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemEntity.java b/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemEntity.java index 5a3d77dac..4ec901dd1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemEntity.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.items.entity; import de.ipb_halle.lbac.admission.ACObjectEntity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import java.util.Date; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemPositionEntity.java b/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemPositionEntity.java index e9ec56134..eb787c267 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemPositionEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/entity/ItemPositionEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.items.entity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/search/ItemSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/items/search/ItemSearchConditionBuilder.java index c5a26fe7d..6b2c69ab5 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/search/ItemSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/search/ItemSearchConditionBuilder.java @@ -23,7 +23,7 @@ import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchConditionBuilder; import de.ipb_halle.lbac.search.SearchRequest; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemEntityGraphBuilder.java index a8cd96c06..b06f260f0 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemEntityGraphBuilder.java @@ -26,7 +26,7 @@ import de.ipb_halle.lbac.material.structure.StructureEntity; import de.ipb_halle.lbac.project.ProjectEntity; import de.ipb_halle.lbac.search.EntityGraphBuilder; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; import javax.persistence.criteria.JoinType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemService.java b/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemService.java index 9582b346b..11cc4c63f 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/service/ItemService.java @@ -48,7 +48,7 @@ import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.search.SearchResultImpl; import de.ipb_halle.lbac.search.lang.Attribute; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.DbField; import de.ipb_halle.lbac.search.lang.EntityGraph; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/Material.java b/ui/src/main/java/de/ipb_halle/lbac/material/Material.java index 6471fd92f..95fb7c207 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/Material.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/Material.java @@ -21,7 +21,7 @@ import de.ipb_halle.lbac.material.common.history.MaterialHistory; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.util.InputConverter; import de.ipb_halle.lbac.material.common.HazardInformation; import de.ipb_halle.lbac.material.common.HazardType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/BioMaterialDifference.java b/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/BioMaterialDifference.java index 08de1cc63..0af4dd902 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/BioMaterialDifference.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/BioMaterialDifference.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.material.biomaterial; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.material.common.bean.MaterialBean; import de.ipb_halle.lbac.material.common.history.HistoryController; import de.ipb_halle.lbac.material.common.history.HistoryEntityId; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/TaxonomyLevel.java b/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/TaxonomyLevel.java index a46b97bd6..3d16a2f04 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/TaxonomyLevel.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/biomaterial/TaxonomyLevel.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.material.biomaterial; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialDetailRightEntity.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialDetailRightEntity.java index f5b06629c..439fc0707 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialDetailRightEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialDetailRightEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.material.common.entity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialEntity.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialEntity.java index 58e2f1310..5b1523b40 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/MaterialEntity.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.material.common.entity; import de.ipb_halle.lbac.admission.ACObjectEntity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import java.util.Date; import javax.persistence.Column; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/index/MaterialIndexEntryEntity.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/index/MaterialIndexEntryEntity.java index c1de82878..dc54dabca 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/index/MaterialIndexEntryEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/entity/index/MaterialIndexEntryEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.material.common.entity.index; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/search/MaterialSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/search/MaterialSearchConditionBuilder.java index 9086b2a53..df9e7f342 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/search/MaterialSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/search/MaterialSearchConditionBuilder.java @@ -25,7 +25,7 @@ import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchConditionBuilder; import de.ipb_halle.lbac.search.SearchRequest; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialEntityGraphBuilder.java index 18e7857d8..c35520766 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialEntityGraphBuilder.java @@ -29,7 +29,7 @@ import de.ipb_halle.lbac.material.structure.StructureEntity; import de.ipb_halle.lbac.project.ProjectEntity; import de.ipb_halle.lbac.search.EntityGraphBuilder; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; import javax.persistence.criteria.JoinType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialService.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialService.java index ba7f4383d..9207f66ca 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/service/MaterialService.java @@ -56,7 +56,7 @@ import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.search.SearchResultImpl; import de.ipb_halle.lbac.search.lang.Attribute; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.DbField; import de.ipb_halle.lbac.search.lang.EntityGraph; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/composition/CompositionDifference.java b/ui/src/main/java/de/ipb_halle/lbac/material/composition/CompositionDifference.java index 7e3b8d69f..03565bebc 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/composition/CompositionDifference.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/composition/CompositionDifference.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.material.composition; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.material.common.bean.MaterialBean; import de.ipb_halle.lbac.material.common.history.MaterialDifference; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/SequenceEntity.java b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/SequenceEntity.java index b50efd2c8..af2cd0877 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/SequenceEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/SequenceEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.material.sequence; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.FieldOrder; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/history/SequenceDifference.java b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/history/SequenceDifference.java index 7b664d697..0af753296 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/history/SequenceDifference.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/history/SequenceDifference.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.Objects; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.material.common.bean.MaterialBean; import de.ipb_halle.lbac.material.common.history.HistoryController; import de.ipb_halle.lbac.material.common.history.MaterialDifference; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilder.java index 7187d637e..c29e5dbfd 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilder.java @@ -20,7 +20,7 @@ import de.ipb_halle.lbac.material.common.search.MaterialSearchConditionBuilder; import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchRequest; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchService.java b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchService.java index cb840ade2..943bb1637 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchService.java @@ -37,7 +37,7 @@ import de.ipb_halle.lbac.material.sequence.SequenceEntity; import de.ipb_halle.lbac.material.sequence.search.SequenceAlignment; import de.ipb_halle.lbac.search.SearchCategory; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.SqlParamTableBuilder; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/structure/MoleculeEntity.java b/ui/src/main/java/de/ipb_halle/lbac/material/structure/MoleculeEntity.java index 5afc83672..f773b27e2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/structure/MoleculeEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/structure/MoleculeEntity.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.material.structure; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/project/Project.java b/ui/src/main/java/de/ipb_halle/lbac/project/Project.java index f25c82708..f8ca641a1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/project/Project.java +++ b/ui/src/main/java/de/ipb_halle/lbac/project/Project.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.material.common.MaterialDetailType; import de.ipb_halle.lbac.search.SearchTarget; diff --git a/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntity.java b/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntity.java index 9013a1520..f8a6943de 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntity.java +++ b/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntity.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.project; import de.ipb_halle.lbac.admission.ACObjectEntity; -import de.ipb_halle.lbac.search.lang.AttributeTag; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.io.Serializable; import java.util.Date; diff --git a/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntityGraphBuilder.java index fd9d30ea3..a1eb940c1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/project/ProjectEntityGraphBuilder.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.admission.MemberEntity; import de.ipb_halle.lbac.search.EntityGraphBuilder; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; import javax.persistence.criteria.JoinType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/project/ProjectSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/project/ProjectSearchConditionBuilder.java index c267e1e25..c798fe09b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/project/ProjectSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/project/ProjectSearchConditionBuilder.java @@ -24,7 +24,7 @@ import de.ipb_halle.lbac.search.SearchConditionBuilder; import de.ipb_halle.lbac.search.SearchRequest; import de.ipb_halle.lbac.search.SearchTarget; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java index b122610ef..982508406 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.reporting.report; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; /** * Report DTO diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/EntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/EntityGraphBuilder.java index 0982955df..ca447b055 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/EntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/EntityGraphBuilder.java @@ -19,7 +19,7 @@ import de.ipb_halle.lbac.admission.ACEntryEntity; import de.ipb_halle.lbac.admission.MembershipEntity; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; import javax.persistence.criteria.JoinType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/SearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/SearchConditionBuilder.java index 76419e739..b28f96ef9 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/SearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/SearchConditionBuilder.java @@ -21,7 +21,7 @@ import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.search.lang.Attribute; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java index 777dbe523..90dc34211 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java @@ -20,8 +20,8 @@ /** * This class stores information about a document. */ -import de.ipb_halle.lbac.file.TermFrequencyList; -import de.ipb_halle.lbac.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequencyList; +import de.ipb_halle.tx.file.TermFrequency; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.message.LocalUUIDConverter; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java index 014ebc126..66f2c094c 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java @@ -18,10 +18,10 @@ package de.ipb_halle.lbac.search.document; import de.ipb_halle.lbac.collections.CollectionEntity; -import de.ipb_halle.lbac.file.FileObjectEntity; -import de.ipb_halle.lbac.file.TermVectorEntity; +import de.ipb_halle.tx.file.FileObjectEntity; +import de.ipb_halle.tx.file.TermVectorEntity; import de.ipb_halle.lbac.search.EntityGraphBuilder; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; import javax.persistence.criteria.JoinType; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchConditionBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchConditionBuilder.java index d1ce1a501..47d847050 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchConditionBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchConditionBuilder.java @@ -23,7 +23,7 @@ import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchConditionBuilder; import de.ipb_halle.lbac.search.SearchRequest; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java index 9cdd7d104..77ae683eb 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java @@ -21,11 +21,11 @@ import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequency; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; -import de.ipb_halle.lbac.file.FileObjectEntity; +import de.ipb_halle.tx.file.FileObject; +import de.ipb_halle.tx.file.FileObjectEntity; import de.ipb_halle.lbac.file.FileSearchRequest; import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchQueryStemmer; @@ -195,10 +195,7 @@ public SearchResult loadDocuments(SearchRequest request) { List entities = q.getResultList(); for (FileObjectEntity entity : entities) { foundDocs.add(convertFileObjectToDocument( - new FileObject( - entity, - collectionService.loadById(entity.getCollection()), - memberService.loadUserById(entity.getUser())))); + new FileObject(entity))); } result.addResults(foundDocs); @@ -313,10 +310,7 @@ public Set loadDocuments(FileSearchRequest request, int limit) { if (count < limit) { documents.add( convertFileObjectToDocument( - new FileObject( - foe, - collectionService.loadById(foe.getCollection()), - memberService.loadUserById(foe.getUser()))) + new FileObject(foe)) ); } } @@ -326,11 +320,11 @@ public Set loadDocuments(FileSearchRequest request, int limit) { private Document convertFileObjectToDocument(FileObject fo) { Document d = new Document(); d.setId(fo.getId()); - d.setCollectionId(fo.getCollection().getId()); + d.setCollectionId(fo.getCollectionId()); d.setNodeId(nodeService.getLocalNodeId()); d.setNode(nodeService.getLocalNode()); d.setLanguage(fo.getDocument_language()); - d.setCollection((Collection) fo.getCollection()); + d.setCollection(collectionService.loadById(fo.getCollectionId())); d.setPath(fo.getFileLocation()); d.setContentType(fo.getName().split("\\.")[fo.getName().split("\\.").length - 1]); d.setOriginalName(fo.getName()); diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java index 4d3b09113..1f534e678 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java @@ -36,13 +36,13 @@ import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.SearchTarget; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.jsf.SendFileBean; +import de.ipb_halle.tx.file.FileObject; /** * diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java index 31a86e307..1be634b24 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java @@ -37,9 +37,10 @@ import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.Collection; +import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; +import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.lbac.webservice.service.LbacWebService; import de.ipb_halle.lbac.webservice.service.NotAuthentificatedException; @@ -57,6 +58,9 @@ public class DocumentWebService extends LbacWebService { @Inject private FileEntityService fileEntityService; + + @Inject + private CollectionService collectionService; @Inject private ACListService acListService; @@ -86,7 +90,7 @@ public Response downloadDocument(DocumentWebRequest request) { return FILE_NOT_FOUND; } - Collection collection = (Collection) fileObject.getCollection(); + Collection collection = collectionService.loadById(fileObject.getCollectionId()); User localUser = memberService.mapRemoteUserToLocalUser(request.getUser(), node); if (!acListService.isPermitted(ACPermission.permREAD, collection, localUser)) { return FORBIDDEN; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/Attribute.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/Attribute.java index ae507bcdb..0dad861cb 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/Attribute.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/Attribute.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTags.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTags.java index 0b06ef876..f9dae373b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTags.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/AttributeTags.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/Condition.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/Condition.java index 937c09ecc..a29becd1c 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/Condition.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/Condition.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import java.util.ArrayList; import java.util.HashSet; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/ConditionValueFetcher.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/ConditionValueFetcher.java index 45917989b..83c88f2ce 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/ConditionValueFetcher.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/ConditionValueFetcher.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import java.util.ArrayList; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/DbField.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/DbField.java index e62670e2b..3e5813aa5 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/DbField.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/DbField.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/EntityGraph.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/EntityGraph.java index 1c17ddd40..25b2290b3 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/EntityGraph.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/EntityGraph.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlBuilder.java index ecd1538b6..fa005dc65 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlBuilder.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilder.java index e1542ebbc..aaf1d1604 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilder.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import com.google.gson.JsonObject; import java.util.ArrayList; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java b/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java index 25957247d..c35582ec2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java @@ -18,11 +18,11 @@ package de.ipb_halle.lbac.search.termvector; import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.file.FileObject; -import de.ipb_halle.lbac.file.TermVector; -import de.ipb_halle.lbac.file.TermVectorEntity; +import de.ipb_halle.tx.file.FileObject; +import de.ipb_halle.tx.file.TermVector; +import de.ipb_halle.tx.file.TermVectorEntity; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.StemmedWordOrigin; +import de.ipb_halle.tx.file.StemmedWordOrigin; import de.ipb_halle.lbac.message.TermVectorMessage; import de.ipb_halle.lbac.search.document.TermOcurrence; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java index 4389a1b2a..42c7c57a2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java @@ -23,12 +23,12 @@ import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.lbac.file.TermFrequency; -import de.ipb_halle.lbac.file.TermFrequencyList; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.search.document.DocumentSearchState; import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.CloudNodeService; +import de.ipb_halle.tx.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequencyList; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java index 00c91eff0..67856882c 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java @@ -19,13 +19,13 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.lbac.file.TermFrequencyList; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.search.document.DocumentSearchState; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.webservice.service.LbacWebService; import de.ipb_halle.lbac.webservice.service.NotAuthentificatedException; +import de.ipb_halle.tx.file.TermFrequencyList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java index bb23eb7a0..a2f519613 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java @@ -18,7 +18,7 @@ package de.ipb_halle.lbac.search.wordcloud; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.lbac.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequency; import java.io.Serializable; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/util/pref/Preference.java b/ui/src/main/java/de/ipb_halle/lbac/util/pref/Preference.java index 6f9009d34..b181fced4 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/util/pref/Preference.java +++ b/ui/src/main/java/de/ipb_halle/lbac/util/pref/Preference.java @@ -18,7 +18,7 @@ package de.ipb_halle.lbac.util.pref; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.entity.DTO; +import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; /** diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java b/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java index 02809851a..3c125bfe8 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java @@ -18,9 +18,9 @@ package de.ipb_halle.lbac.collections.mock; import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.file.FileObject; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.file.FileEntityService; +import de.ipb_halle.tx.file.FileObject; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java index 8d38f276e..8bdf28203 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java @@ -26,7 +26,9 @@ import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; +import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import de.ipb_halle.tx.file.TermVector; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -95,13 +97,13 @@ public void testSave() { col=collectionService.save(col); FileObject fE = new FileObject(); - fE.setCollection(col); + fE.setCollectionId(col.getId()); fE.setCreated(new Date()); fE.setDocument_language("en"); fE.setFileLocation("testFile.pdf"); fE.setHash("testHash"); fE.setName("testFile"); - fE.setUser(u); + fE.setUserId(u.getId()); fE=fileEntityService.save(fE); diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java index 02c9017a3..99771c2f9 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java @@ -27,6 +27,7 @@ import de.ipb_halle.lbac.file.mock.HttpServletResponseMock.WriterMock; import de.ipb_halle.lbac.file.mock.UploadToColMock; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import de.ipb_halle.tx.file.FileObject; import java.io.File; import java.nio.file.Paths; import java.util.Arrays; diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java b/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java index 09bb0cb8a..ed8a7a3b8 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.file.mock; import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.file.FileObject; import de.ipb_halle.lbac.file.FileEntityService; +import de.ipb_halle.tx.file.FileObject; /** * diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java b/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java index a99a17761..bec6644db 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java @@ -22,7 +22,7 @@ import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.file.UploadToCol; -import de.ipb_halle.lbac.file.save.AttachmentHolder; +import de.ipb_halle.tx.file.AttachmentHolder; import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import java.io.InputStream; import javax.servlet.AsyncContext; diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java index b725da352..9316f33d3 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java @@ -18,8 +18,8 @@ package de.ipb_halle.lbac.file.save; import de.ipb_halle.lbac.file.FilterDefinitionInputStreamFactory; -import de.ipb_halle.lbac.file.StemmedWordOrigin; -import de.ipb_halle.lbac.file.TermVector; +import de.ipb_halle.tx.file.StemmedWordOrigin; +import de.ipb_halle.tx.file.TermVector; import java.io.FileNotFoundException; import java.util.List; import java.util.Set; diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java index 07fec7118..9e871edb8 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java @@ -24,9 +24,9 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.project.ProjectService; +import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.File; import java.io.FileInputStream; @@ -100,10 +100,10 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS Map cmap = new HashMap<>(); cmap.put("id", id); FileObject fo = fileEntityService.load(cmap).get(0); - Assert.assertEquals(col.getId(), fo.getCollection().getId()); + Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("en", fo.getDocument_language()); Assert.assertEquals("Document1.pdf", fo.getName()); - Assert.assertEquals(publicUser.getId(), fo.getUser().getId()); + Assert.assertEquals(publicUser.getId(), fo.getUserId()); f = new File(exampleDocsRootFolder + "DocumentX.docx"); stream = new FileInputStream(f); @@ -111,10 +111,10 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS Assert.assertEquals(2, fileEntityService.getDocumentCount(col)); cmap.put("id", id); fo = fileEntityService.load(cmap).get(0); - Assert.assertEquals(col.getId(), fo.getCollection().getId()); + Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("en", fo.getDocument_language()); Assert.assertEquals("DocumentX.docx", fo.getName()); - Assert.assertEquals(publicUser.getId(), fo.getUser().getId()); + Assert.assertEquals(publicUser.getId(), fo.getUserId()); f = new File(exampleDocsRootFolder + "TestTabelle.xlsx"); stream = new FileInputStream(f); @@ -122,10 +122,10 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS Assert.assertEquals(3, fileEntityService.getDocumentCount(col)); cmap.put("id", id); fo = fileEntityService.load(cmap).get(0); - Assert.assertEquals(col.getId(), fo.getCollection().getId()); + Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("en", fo.getDocument_language()); Assert.assertEquals("TestTabelle.xlsx", fo.getName()); - Assert.assertEquals(publicUser.getId(), fo.getUser().getId()); + Assert.assertEquals(publicUser.getId(), fo.getUserId()); f = new File(exampleDocsRootFolder + "TestTabelle.xlsx"); stream = new FileInputStream(f); @@ -134,11 +134,11 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS Assert.assertEquals(4, fileEntityService.getDocumentCount(col)); cmap.put("id", id); fo = fileEntityService.load(cmap).get(0); - Assert.assertEquals(col.getId(), fo.getCollection().getId()); + Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("de", fo.getDocument_language()); Assert.assertEquals("a9eed28584c7e6df1d061c77884820524a7d2b4c6644ef5d13b0c2daedaf4d10d040b7c7380df448f91a28eb7fba94cf0b4a964ae141032c63a0b571aeaa5ccf", fo.getHash()); Assert.assertEquals("TestTabelle.xlsx", fo.getName()); - Assert.assertEquals(publicUser.getId(), fo.getUser().getId()); + Assert.assertEquals(publicUser.getId(), fo.getUserId()); // Assert.assertEquals(fileSaver.getFileLocation(), Paths.get(col.getBaseFolder(), "0", "0", fo.getFilename()).toString()); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilderTest.java b/ui/src/test/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilderTest.java index edfcec1ad..9514c4f99 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilderTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/material/sequence/search/service/SequenceSearchConditionBuilderTest.java @@ -23,7 +23,7 @@ import de.ipb_halle.lbac.material.common.search.MaterialSearchRequestBuilder; import de.ipb_halle.lbac.material.common.service.MaterialEntityGraphBuilder; import de.ipb_halle.lbac.material.sequence.SequenceType; -import de.ipb_halle.lbac.search.lang.AttributeType; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.Condition; import de.ipb_halle.lbac.search.lang.EntityGraph; import de.ipb_halle.lbac.search.lang.Operator; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java index f089474e5..bfd9c5270 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java @@ -48,7 +48,6 @@ import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.NetObjectImpl; import de.ipb_halle.lbac.search.bean.NetObjectFactory; @@ -58,6 +57,7 @@ import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; +import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** @@ -231,12 +231,12 @@ private void assertNoFileSent() { private FileObject createFileObject(String location) { FileObject fO = new FileObject(); - fO.setCollection(readableCollection); + fO.setCollectionId(readableCollection.getId()); fO.setCreated(new Date()); fO.setDocument_language("en"); fO.setFileLocation(location); fO.setName(location); - fO.setUser(publicUser); + fO.setUserId(publicUser.getId()); return fO; } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java index 739e668eb..88754b8eb 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java @@ -49,13 +49,13 @@ import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FileObject; import de.ipb_halle.lbac.globals.KeyManager; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.webclient.LbacWebClient; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; +import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** @@ -180,12 +180,12 @@ public void test_downloadDocument_successfulDownload() throws Exception { private FileObject createFileObject(String location, Collection collection) { FileObject fO = new FileObject(); - fO.setCollection(collection); + fO.setCollectionId(collection.getId()); fO.setCreated(new Date()); fO.setDocument_language("en"); fO.setFileLocation(location); fO.setName(location); - fO.setUser(adminUser); + fO.setUserId(adminUser.getId()); return fO; } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/lang/ConditionTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/lang/ConditionTest.java index 28e6ac045..dc7e561f5 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/lang/ConditionTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/lang/ConditionTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import org.junit.Assert; import org.junit.jupiter.api.Test; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/lang/HorrorEntity.java b/ui/src/test/java/de/ipb_halle/lbac/search/lang/HorrorEntity.java index 6221813ac..31d0c703b 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/lang/HorrorEntity.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/lang/HorrorEntity.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeTag; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.admission.ACObjectEntity; import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverrides; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlBuilderTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlBuilderTest.java index 208dbd93c..cbb72b0b9 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlBuilderTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlBuilderTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.entity.NodeEntity; import de.ipb_halle.lbac.items.entity.ItemEntity; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilderTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilderTest.java index 08341b1c9..3c536e45a 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilderTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/lang/SqlParamTableBuilderTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.lang; +import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.AdmissionSubSystemType; import de.ipb_halle.lbac.base.JsonAssert; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java index 941ca0953..815a72394 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java @@ -20,9 +20,9 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.TermFrequency; -import de.ipb_halle.lbac.file.TermFrequencyList; import de.ipb_halle.lbac.search.document.StemmedWordGroup; +import de.ipb_halle.tx.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequencyList; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java index dec5fdad4..5f07f1fcb 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java @@ -22,14 +22,14 @@ import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.file.FileObject; -import de.ipb_halle.lbac.file.TermVector; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.StemmedWordOrigin; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import de.ipb_halle.tx.file.FileObject; +import de.ipb_halle.tx.file.TermVector; +import de.ipb_halle.tx.file.StemmedWordOrigin; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -193,13 +193,13 @@ private Collection createCollection(String name, ACList acl, User owner) { private FileObject createFileObject(Collection col, String language, String fileName, User owner) { FileObject fO = new FileObject(); - fO.setCollection(col); + fO.setCollectionId(col.getId()); fO.setCreated(new Date()); fO.setDocument_language(language); fO.setFileLocation(fileName); fO.setHash(fileName); fO.setName(fileName); - fO.setUser(owner); + fO.setUserId(owner.getId()); return fO; } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java index 9e2680c6d..7b6dae68c 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java @@ -18,7 +18,7 @@ package de.ipb_halle.lbac.search.wordcloud; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.lbac.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequency; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java index 3c7e6e3bc..c272527fd 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java @@ -20,10 +20,10 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.TermFrequency; -import de.ipb_halle.lbac.file.TermFrequencyList; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.search.wordcloud.WordCloudWebRequest; +import de.ipb_halle.tx.file.TermFrequency; +import de.ipb_halle.tx.file.TermFrequencyList; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.POST; diff --git a/ui/src/test/resources/test-persistence.xml b/ui/src/test/resources/test-persistence.xml index 74ff27ed6..9f1021c93 100644 --- a/ui/src/test/resources/test-persistence.xml +++ b/ui/src/test/resources/test-persistence.xml @@ -80,7 +80,6 @@ de.ipb_halle.lbac.entity.CloudEntity de.ipb_halle.lbac.entity.CloudNodeEntity de.ipb_halle.lbac.collections.CollectionEntity - de.ipb_halle.lbac.file.FileObjectEntity de.ipb_halle.lbac.admission.GroupEntity de.ipb_halle.lbac.entity.InfoObjectEntity de.ipb_halle.lbac.admission.MemberEntity @@ -88,12 +87,13 @@ de.ipb_halle.lbac.admission.NestingPathEntity de.ipb_halle.lbac.admission.NestingPathSetEntity de.ipb_halle.lbac.entity.NodeEntity - de.ipb_halle.lbac.file.TermVectorEntity de.ipb_halle.lbac.admission.UserEntity de.ipb_halle.lbac.forum.TopicEntity de.ipb_halle.lbac.forum.PostingEntity de.ipb_halle.lbac.util.pref.PreferenceEntity de.ipb_halle.lbac.reporting.report.ReportEntity + de.ipb_halle.tx.file.FileObjectEntity + de.ipb_halle.tx.file.TermVectorEntity de.ipb_halle.lbac.search.lang.HorrorEntity diff --git a/ui/web/WEB-INF/persistence.xml b/ui/web/WEB-INF/persistence.xml index cdd437067..b78ee2645 100644 --- a/ui/web/WEB-INF/persistence.xml +++ b/ui/web/WEB-INF/persistence.xml @@ -72,7 +72,6 @@ de.ipb_halle.lbac.entity.CloudEntity de.ipb_halle.lbac.entity.CloudNodeEntity de.ipb_halle.lbac.collections.CollectionEntity - de.ipb_halle.lbac.file.FileObjectEntity de.ipb_halle.lbac.admission.GroupEntity de.ipb_halle.lbac.entity.InfoObjectEntity de.ipb_halle.lbac.admission.MemberEntity @@ -80,7 +79,6 @@ de.ipb_halle.lbac.admission.NestingPathEntity de.ipb_halle.lbac.admission.NestingPathSetEntity de.ipb_halle.lbac.entity.NodeEntity - de.ipb_halle.lbac.file.TermVectorEntity de.ipb_halle.lbac.admission.UserEntity de.ipb_halle.lbac.forum.TopicEntity de.ipb_halle.lbac.forum.PostingEntity @@ -99,6 +97,8 @@ de.ipb_halle.lbac.items.entity.ItemEntity de.ipb_halle.lbac.items.entity.ItemHistoryEntity de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity + de.ipb_halle.tx.file.FileObjectEntity + de.ipb_halle.tx.file.TermVectorEntity true From 6240bdd20635af0d0bc5331b930e129830a9c353 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Tue, 5 Sep 2023 11:25:57 +0200 Subject: [PATCH 03/28] add test --- tx/pom.xml | 414 ++++++++++++------ .../ipb_halle/tx/service/TextWebService.java | 50 +++ tx/src/main/resources/log4j2.xml | 42 ++ tx/src/main/webapp/WEB-INF/beans.xml | 24 + tx/src/main/webapp/WEB-INF/web.xml | 38 ++ .../tx/service/TextWebServiceTest.java | 77 ++++ tx/src/test/resources/log4j2-test.xml | 42 ++ tx/src/test/resources/test-persistence.xml | 57 +++ 8 files changed, 601 insertions(+), 143 deletions(-) create mode 100644 tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java create mode 100644 tx/src/main/resources/log4j2.xml create mode 100644 tx/src/main/webapp/WEB-INF/beans.xml create mode 100644 tx/src/main/webapp/WEB-INF/web.xml create mode 100644 tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java create mode 100644 tx/src/test/resources/log4j2-test.xml create mode 100644 tx/src/test/resources/test-persistence.xml diff --git a/tx/pom.xml b/tx/pom.xml index f2c14ba08..15a9651f4 100644 --- a/tx/pom.xml +++ b/tx/pom.xml @@ -17,153 +17,281 @@ --> - 4.0.0 + 4.0.0 - de.ipb-halle - tx - 1.0.1 - jar + de.ipb-halle + tx + 1.0.1 + jar - tx - http://www.ipb-halle.de + tx + http://www.ipb-halle.de - - UTF-8 - - 1.28.3 - - - - - - central - https://repo.maven.apache.org/maven2/ - - - sonatype public - https://oss.sonatype.org/content/groups/public/ - - + + UTF-8 + + 1.28.3 + 5.3.26.Final + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - -Xlint:all - ${project.build.sourceEncoding} - - - - - - maven-assembly-plugin - 2.6 - - - jar-with-dependencies - - - - de.ipb_halle.tx.Tx - - - - - - make-assembly - package - - single - - - - - - - - - - - - - - de.ipb-halle - tx-api - 1.0.1 - - - - - commons-cli - commons-cli - 1.3.1 - - - - - org.apache.commons - commons-csv - 1.5 - - - - - org.slf4j - slf4j-api - 1.7.30 - - - - - org.apache.tika - tika-core - ${tika.version} - - - - org.apache.tika - tika-langdetect - ${tika.version} - - - - org.apache.tika - tika-parsers - ${tika.version} - - - - - org.rabinfingerprint - rabinfingerprint - 1.0.0-SNAPSHOT - - - - - org.tartarus - snowball - 2.0.0 - - - - - junit - junit - 4.13.1 - test - - - + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + de.ipb_halle.tx.Tx + + + + + + make-assembly + package + + single + + + + + + + + + + + + + + de.ipb-halle + tx-api + 1.0.1 + + + + + commons-cli + commons-cli + 1.3.1 + + + + + org.apache.tomee + javaee-api + 7.0-1 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + + + org.apache.commons + commons-csv + 1.5 + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + + org.apache.tika + tika-core + ${tika.version} + + + + org.apache.tika + tika-langdetect + ${tika.version} + + + + org.apache.tika + tika-parsers + ${tika.version} + + + + + org.rabinfingerprint + rabinfingerprint + 1.0.0-SNAPSHOT + + + + + org.tartarus + snowball + 2.0.0 + + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-ehcache + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-core + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-validator + 5.3.6.Final + provided + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + test + + + org.testcontainers + postgresql + 1.16.3 + test + + + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha6 + test + + + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.6 + pom + test + + + + + org.apache.tomee + arquillian-openejb-embedded + 7.0.5 + test + + + org.awaitility + awaitility + 4.0.3 + test + + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + commons-io + commons-io + + + + + + diff --git a/tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java b/tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java new file mode 100644 index 000000000..6b0ce388e --- /dev/null +++ b/tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java @@ -0,0 +1,50 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.tx.service; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.inject.Inject; + +import javax.servlet.AsyncContext; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +@WebServlet(name = "TextWebService", urlPatterns = {"/process/*"}, asyncSupported = true) +public class TextWebService extends HttpServlet { + + private final static long serialVersionUID = 1L; + + private final Logger logger = LogManager.getLogger(TextWebService.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + logger.info("doGet(): request received."); + try { + final PrintWriter out = resp.getWriter(); + out.write("Success"); + } catch (IOException e) { + logger.error((Throwable) e); + } + } +} diff --git a/tx/src/main/resources/log4j2.xml b/tx/src/main/resources/log4j2.xml new file mode 100644 index 000000000..692761c6b --- /dev/null +++ b/tx/src/main/resources/log4j2.xml @@ -0,0 +1,42 @@ + + + + + /usr/local/tomee/logs/ + + + + + + + + + + + [%-5p] %d{yyyy-MM-dd HH:mm:ss} %c{1} - %m%n + + + + + + + + + + + + + + + + + + + + + + diff --git a/tx/src/main/webapp/WEB-INF/beans.xml b/tx/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 000000000..dfd60e317 --- /dev/null +++ b/tx/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,24 @@ + + + + diff --git a/tx/src/main/webapp/WEB-INF/web.xml b/tx/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..05a727317 --- /dev/null +++ b/tx/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,38 @@ + + + + CRIMSy module for text / document processing and knowledge extraction + Knowledge Extractor + + + Text Processing Service + TextWebService + de.ipb_halle.tx.service.TextWebService + 1 + + + TextWebService + /process/* + + + + + + diff --git a/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java b/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java new file mode 100644 index 000000000..76c9851f1 --- /dev/null +++ b/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java @@ -0,0 +1,77 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.tx.service; + +import java.io.IOException; +import javax.inject.Inject; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.HttpClientBuilder; +import org.eclipse.jetty.http.HttpStatus; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +/** + * + * @author fblocal + */ + +// @ExtendWith(PostgresqlContainerExtension.class) +@ExtendWith(ArquillianExtension.class) +public class TextWebServiceTest { + + @Inject + private TextWebService textWebService; + + + @Deployment + public static WebArchive createDeployment() { + WebArchive archive = ShrinkWrap.create(WebArchive.class, "TextWebServiceTest.war") + .addClass(TextWebService.class) + .addAsWebInfResource("test-persistence.xml", "persistence.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") + .addAsResource("javax.enterprise.inject.spi.Extension", + "META-INF/services/javax.enterprise.inject.spi.Extension"); + return archive; + } + + @BeforeEach + public void init() { + + } + + @Test +// @RunAsClient + public void test001_TextWebService() throws IOException { + HttpUriRequest request = new HttpGet("localhost:8080/tx/process/500"); + HttpResponse response = HttpClientBuilder.create().build().execute(request); + + assertEquals(response.getStatusLine().getStatusCode(), HttpStatus.OK_200); + } + +} diff --git a/tx/src/test/resources/log4j2-test.xml b/tx/src/test/resources/log4j2-test.xml new file mode 100644 index 000000000..b443917b4 --- /dev/null +++ b/tx/src/test/resources/log4j2-test.xml @@ -0,0 +1,42 @@ + + + + + target/logs + + + + + + + + + + + [%-5p] %d{yyyy-MM-dd HH:mm:ss} %c{1} - %m%n + + + + + + + + + + + + + + + + + + + + + + diff --git a/tx/src/test/resources/test-persistence.xml b/tx/src/test/resources/test-persistence.xml new file mode 100644 index 000000000..718a9dd8a --- /dev/null +++ b/tx/src/test/resources/test-persistence.xml @@ -0,0 +1,57 @@ + + + + + + + + LBAC api + apiDS + + org.hibernate.jpa.HibernatePersistenceProvider + + de.ipb_halle.tx.file.FileObjectEntity + de.ipb_halle.tx.file.TermVectorEntity + + true + + + + + + + + + + + + + + + + + From a2953c71b2b20d6c4312f8c1025ea9484db9edb0 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Wed, 6 Sep 2023 13:13:34 +0200 Subject: [PATCH 04/28] try fix: test still not working --- tx/pom.xml | 144 ++- .../PostgresqlContainerExtension.java | 87 ++ .../tx/service/TextWebServiceTest.java | 31 +- tx/src/test/resources/arquillian.xml | 43 + tx/src/test/resources/schema/00001.sql | 963 ++++++++++++++++++ tx/src/test/resources/test-persistence.xml | 6 +- 6 files changed, 1253 insertions(+), 21 deletions(-) create mode 100644 tx/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java create mode 100644 tx/src/test/resources/arquillian.xml create mode 100644 tx/src/test/resources/schema/00001.sql diff --git a/tx/pom.xml b/tx/pom.xml index 15a9651f4..8befa166c 100644 --- a/tx/pom.xml +++ b/tx/pom.xml @@ -25,10 +25,17 @@ de.ipb-halle tx 1.0.1 - jar tx http://www.ipb-halle.de + + + scm:git:https://github.com/ipb-halle/CRIMSy.git + https://github.com/ipb-halle/CRIMSy.git + + war + + UTF-8 @@ -66,7 +73,18 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + validate + + create + + + + + true + true + 8 + + {0} TX (git-sha1:{2} * {1,date,yyyy-MM-dd HH:mm:ss}) + + ${project.version} + timestamp + scmVersion + + + + + + + + + + + + maven-war-plugin + 2.4 + + web + true + ui + + + true + + + ${buildNumber} + + + + + @@ -115,7 +213,7 @@ org.apache.tomee javaee-api - 7.0-1 + 7.0-2 @@ -213,6 +311,29 @@ provided + + + org.postgresql + postgresql + 42.4.3 + + + + + org.glassfish + javax.el + 3.0.1-b12 + test + + + @@ -268,10 +389,24 @@ org.apache.tomee - arquillian-openejb-embedded - 7.0.5 + arquillian-tomee-embedded + 7.0.9 + test + + org.awaitility awaitility @@ -292,6 +427,5 @@ - diff --git a/tx/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java b/tx/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java new file mode 100644 index 000000000..d940c26f6 --- /dev/null +++ b/tx/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java @@ -0,0 +1,87 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.testcontainers; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.containers.Container.ExecResult; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +/** + * JUnit5 extension that starts a PostgreSQL docker container once + * before all tests are invoked. Testcontainer's singleton + * container pattern is employed, thus the container is teared down when the + * JVM shuts down. The docker container exposed its port 5432 to the host on + * port 65432 to avoid conflicts with existing PostgreSQL installations. + *

+ * Usage: Annotate the test class with + * {@code @ExtendWith(PostgresqlContainerExtension.class)} + * + * @author flange + */ +public class PostgresqlContainerExtension implements BeforeAllCallback { + private static final String[] SCHEMA_FILES = { "00001.sql" }; + private static final String IMAGE_NAME = "ipbhalle/crimsydb:bingo_pg12"; + + private static final AtomicBoolean FIRST_RUN = new AtomicBoolean(true); + private PostgreSQLContainer container; + + private Logger logger = LogManager.getLogger(this.getClass().getName()); + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + if (FIRST_RUN.getAndSet(false)) { + startContainer(); + } + } + + @SuppressWarnings("resource") + private void startContainer() throws Exception { + DockerImageName customPostgresImage = DockerImageName.parse(IMAGE_NAME).asCompatibleSubstituteFor("postgres"); + container = new PostgreSQLContainer<>(customPostgresImage).withUsername("postgres"); + container.setPortBindings(Arrays.asList("65432:5432")); + + logger.info("Starting Postgresql container with image " + customPostgresImage.toString()); + container.start(); + + for (String schemaFile : SCHEMA_FILES) { + copySchema("schema/" + schemaFile); + applySchema(schemaFile); + } + } + + private void copySchema(String filename) { + logger.info("Copy schema file " + filename + " into container path /"); + container.copyFileToContainer(MountableFile.forClasspathResource(filename), "/"); + } + + private void applySchema(String filename) throws Exception { + logger.info("Load schema file /" + filename); + ExecResult result = container.execInContainer("su", "postgres", "-c psql < /" + filename); + logger.info("Stdout: " + result.getStdout()); + logger.error("Stderr: " + result.getStderr()); + } +} diff --git a/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java b/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java index 76c9851f1..0a13036b3 100644 --- a/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java +++ b/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java @@ -17,7 +17,9 @@ */ package de.ipb_halle.tx.service; +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.IOException; +import java.net.URL; import javax.inject.Inject; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -36,42 +38,47 @@ import org.junit.jupiter.api.extension.ExtendWith; + /** * * @author fblocal */ -// @ExtendWith(PostgresqlContainerExtension.class) +@ExtendWith(PostgresqlContainerExtension.class) @ExtendWith(ArquillianExtension.class) public class TextWebServiceTest { - @Inject - private TextWebService textWebService; + +// @Inject +// private TextWebService textWebService; - @Deployment + @Deployment public static WebArchive createDeployment() { + System.setProperty("log4j.configurationFile", "log4j2-test.xml"); + WebArchive archive = ShrinkWrap.create(WebArchive.class, "TextWebServiceTest.war") .addClass(TextWebService.class) .addAsWebInfResource("test-persistence.xml", "persistence.xml") - .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") - .addAsResource("javax.enterprise.inject.spi.Extension", - "META-INF/services/javax.enterprise.inject.spi.Extension"); + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); +// .addAsResource("javax.enterprise.inject.spi.Extension", +// "META-INF/services/javax.enterprise.inject.spi.Extension"); return archive; } - @BeforeEach + // @BeforeEach public void init() { } @Test -// @RunAsClient + @RunAsClient public void test001_TextWebService() throws IOException { - HttpUriRequest request = new HttpGet("localhost:8080/tx/process/500"); + // port number must match the arquillian setting + HttpUriRequest request = new HttpGet("http://localhost:8800/tx/process/"); HttpResponse response = HttpClientBuilder.create().build().execute(request); - - assertEquals(response.getStatusLine().getStatusCode(), HttpStatus.OK_200); + + assertEquals(HttpStatus.OK_200, response.getStatusLine().getStatusCode()); } } diff --git a/tx/src/test/resources/arquillian.xml b/tx/src/test/resources/arquillian.xml new file mode 100644 index 000000000..444537665 --- /dev/null +++ b/tx/src/test/resources/arquillian.xml @@ -0,0 +1,43 @@ + + + + + + + 8800 + + + + target/arquillian + + apiDS = new://Resource?type=DataSource + apiDS.JdbcDriver = org.postgresql.Driver + apiDS.JdbcUrl = jdbc:postgresql://localhost:65432/lbac?charSet=UTF-8 + apiDS.UserName = lbac + apiDS.Password = lbac + apiDS.JtaManaged = true + apiDS.LogSql = true + + true + + + + + + + diff --git a/tx/src/test/resources/schema/00001.sql b/tx/src/test/resources/schema/00001.sql new file mode 100644 index 000000000..cc5a529c1 --- /dev/null +++ b/tx/src/test/resources/schema/00001.sql @@ -0,0 +1,963 @@ +/* + * Leibniz Bioactives Cloud + * Init script for database postgres 12.6 + * + * Copyright 2017 Leibniz-Institut f. Pflanzenbiochemie + * + * 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. + * + */ + +/* + * define global vars + */ +\set LBAC_SCHEMA_VERSION '\'00001\'' + +\set LBAC_SCHEMA lbac +\set LBAC_DATABASE lbac +\set LBAC_USER lbac +\set LBAC_PW lbac +-- quoted stuff -- +\set LBAC_SCHEMA_QUOTED '\'' :LBAC_SCHEMA '\'' +\set LBAC_DATABASE_QUOTED '\'' :LBAC_DATABASE '\'' +\set LBAC_USER_QUOTED '\'' :LBAC_USER '\'' +\set LBAC_PW_QUOTED '\'' :LBAC_PW '\'' + +/* + *========================================================= + * + * terminate all session from database lbac -- + * getting db exclusive -- + */ +SELECT pg_terminate_backend(pg_stat_activity.pid) +FROM pg_stat_activity +WHERE pg_stat_activity.datname = :LBAC_DATABASE_QUOTED + AND pid <> pg_backend_pid(); + +/* + * clean up + */ +-- the following statement fails if LBAC_USER is not known! +-- REASSIGN OWNED BY :LBAC_USER TO postgres; +DROP SCHEMA IF EXISTS :LBAC_SCHEMA CASCADE; +DROP DATABASE IF EXISTS :LBAC_DATABASE; +DROP USER IF EXISTS :LBAC_USER; + +/* + * (re-)create database objects + */ +-- roles -- +CREATE USER :LBAC_USER PASSWORD :LBAC_PW_QUOTED; +-- db -- +CREATE DATABASE :LBAC_DATABASE WITH ENCODING 'UTF8' OWNER :LBAC_USER; + +\connect :LBAC_DATABASE + +-- Bingo database extension (chemistry) -- +\i /opt/bingo/bingo_install.sql +GRANT USAGE ON SCHEMA bingo TO :LBAC_USER; +GRANT SELECT ON bingo.bingo_config TO :LBAC_USER; +GRANT SELECT ON bingo.bingo_tau_config TO :LBAC_USER; + +-- schema -- +CREATE SCHEMA AUTHORIZATION :LBAC_USER; + +-- adjust schema search path -- +ALTER USER :LBAC_USER SET search_path to :LBAC_SCHEMA,public; + +-- privileges -- +GRANT USAGE ON SCHEMA :LBAC_SCHEMA, public TO :LBAC_USER; +GRANT CONNECT, TEMPORARY, TEMP ON DATABASE :LBAC_DATABASE to :LBAC_USER; +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA :LBAC_SCHEMA to :LBAC_USER; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to :LBAC_USER; +REVOKE ALL ON ALL TABLES IN SCHEMA :LBAC_SCHEMA FROM public; + +-- check for usefull extensions and install it -- +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +\connect - :LBAC_USER + +BEGIN TRANSACTION; + +-- tables -- + +/* + * Nodes, Clouds, etc. + */ +CREATE TABLE nodes ( + id UUID NOT NULL PRIMARY KEY, + baseUrl VARCHAR NOT NULL, + institution VARCHAR NOT NULL, + local BOOLEAN NOT NULL DEFAULT FALSE, + publicNode BOOLEAN NOT NULL DEFAULT FALSE, + version VARCHAR NOT NULL DEFAULT '00001', + publickey VARCHAR NOT NULL DEFAULT 'dummy' +); + +CREATE TABLE clouds ( + id BIGSERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + UNIQUE (name) +); +INSERT INTO clouds (name) VALUES ('TESTCLOUD'); + +CREATE TABLE cloud_nodes ( + id BIGSERIAL NOT NULL PRIMARY KEY, + node_id UUID NOT NULL REFERENCES nodes(id) ON UPDATE CASCADE ON DELETE CASCADE, + cloud_id BIGINT NOT NULL REFERENCES clouds(id) ON UPDATE CASCADE ON DELETE CASCADE, + rank INTEGER NOT NULL DEFAULT 1, + publickey VARCHAR NOT NULL DEFAULT 'dummy', + failures INTEGER NOT NULL DEFAULT 0, + retrytime BIGINT NOT NULL DEFAULT 0, + UNIQUE (cloud_id, node_id) +); + + +/* definition of master node (will be skipped on master node) */ + +/* definition of local node */ +INSERT INTO nodes (id, baseUrl, institution, local) VALUES + ( '986ad1be-9a3b-4a70-8600-c489c2a00da4', + 'http://localhost/', + 'TEST', True); + +INSERT INTO cloud_nodes (node_id, cloud_id, rank) + SELECT '986ad1be-9a3b-4a70-8600-c489c2a00da4'::UUID AS node_id, id AS cloud_id, 10 AS rank + FROM clouds WHERE name='TESTCLOUD'; + +/* + * Admission + * users, groups, memberships + */ +CREATE TABLE usersGroups ( + id SERIAL NOT NULL PRIMARY KEY, + memberType VARCHAR(1) NOT NULL, + subSystemType INTEGER, + subSystemData VARCHAR, + modified TIMESTAMP DEFAULT now(), + node_id UUID REFERENCES nodes(id) ON UPDATE CASCADE ON DELETE CASCADE, + login VARCHAR, + name VARCHAR, + email VARCHAR, + password VARCHAR, + phone VARCHAR, + shortcut VARCHAR CHECK (shortcut ~ '^[A-Z]+$') UNIQUE +); + +CREATE TABLE memberships ( + id SERIAL NOT NULL PRIMARY KEY, + group_id INTEGER NOT NULL REFERENCES usersGroups (id) ON UPDATE CASCADE ON DELETE CASCADE, + member_id INTEGER NOT NULL REFERENCES usersGroups (id) ON UPDATE CASCADE ON DELETE CASCADE, + nested BOOLEAN NOT NULL DEFAULT FALSE, + UNIQUE(group_id, member_id) +); + +CREATE INDEX i_memberships_group ON memberships (group_id); +CREATE INDEX i_memberships_member ON memberships (member_id); + + +CREATE TABLE nestingpathsets ( + id SERIAL NOT NULL PRIMARY KEY, + membership_id INTEGER NOT NULL REFERENCES memberships(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +/* + * ToDo: Do some database sanitation for memberships. + * This cleanup / sanitation is necessary, when the membership table + * is manipulated out of band, because the table 'nestingpathsets' is not + * covered by referential integrity, i.e. deleting memberships via + * 'DELETE FROM memberships ...' will cover all other tables but not + * 'nestingpathsets'. + * + * CREATE OR REPLACE FUNCTION cleanNestingPathSets ( id INTEGER ) RETURNS INTEGER + * ... + * CREATE TRIGGER ... AFTER DELETE ON nestingpathset_memberships EXECUTE PROCEDURE cleanNestingPathSets(OLD.nestingpathset_id); + * + * Alternatively on could run the following SQL command manually: + * + * DELETE FROM nestingpathsets AS np USING (SELECT id FROM nestingpathsets + * EXCEPT SELECT nestingpathset_id FROM nestingpathset_memberships) AS e WHERE np.id=e.id; + * + */ + +CREATE TABLE nestingpathset_memberships ( + nestingpathsets_id INTEGER NOT NULL REFERENCES nestingpathsets(id) ON UPDATE CASCADE ON DELETE CASCADE, + memberships_id INTEGER NOT NULL REFERENCES memberships(id) ON UPDATE CASCADE ON DELETE CASCADE, + UNIQUE(nestingpathsets_id, memberships_id) +); + +/* + * Admission + * ACLs + */ +CREATE TABLE aclists ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + permCode INTEGER +); + +CREATE TABLE acentries ( + aclist_id INTEGER NOT NULL REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + member_id INTEGER NOT NULL REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + permRead BOOLEAN NOT NULL DEFAULT FALSE, + permEdit BOOLEAN NOT NULL DEFAULT FALSE, + permCreate BOOLEAN NOT NULL DEFAULT FALSE, + permDelete BOOLEAN NOT NULL DEFAULT FALSE, + permChown BOOLEAN NOT NULL DEFAULT FALSE, + permGrant BOOLEAN NOT NULL DEFAULT FALSE, + permSuper BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY(aclist_id, member_id) +); + +/* + * Info + */ +CREATE TABLE info ( + key VARCHAR NOT NULL PRIMARY KEY, + value VARCHAR, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +INSERT INTO info (key, value, owner_id, aclist_id) VALUES ('DBSchema Version', '00000', null, null); + +/* + * Collections and other distributed resources + */ +CREATE TABLE collections ( + id SERIAL NOT NULL PRIMARY KEY, + description VARCHAR, + name VARCHAR, + indexPath VARCHAR, + storagePath VARCHAR, + owner_id INTEGER NOT NULL REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + aclist_id INTEGER NOT NULL REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE files ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + filename VARCHAR NOT NULL, + hash VARCHAR, + created TIMESTAMP DEFAULT now(), + user_id INTEGER REFERENCES usersGroups (id) ON DELETE SET NULL, + collection_id INTEGER NOT NULL REFERENCES collections (id) ON UPDATE CASCADE ON DELETE CASCADE, + document_language VARCHAR NOT NULL DEFAULT 'en' +); + +CREATE TABLE topics ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + category VARCHAR, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + cloud_name VARCHAR NOT NULL DEFAULT 'cloudONE' REFERENCES clouds(name) ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE postings ( + id SERIAL NOT NULL PRIMARY KEY, + text VARCHAR, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + topic_id INTEGER REFERENCES topics(id) ON UPDATE CASCADE ON DELETE CASCADE, + created TIMESTAMP DEFAULT now() +); + + +/* + * Termvector table + */ +CREATE TABLE termvectors ( + wordroot VARCHAR NOT NULL, + file_id INTEGER NOT NULL REFERENCES files (id) ON UPDATE CASCADE ON DELETE CASCADE, + termfrequency INTEGER NOT NULL, + PRIMARY KEY(wordroot, file_id) +); +CREATE INDEX i_termvectors_file_id ON termvectors (file_id); + + +CREATE TABLE unstemmed_words( + stemmed_word VARCHAR NOT NULL, + file_id INTEGER NOT NULL REFERENCES files (id) ON UPDATE CASCADE ON DELETE CASCADE, + unstemmed_word VARCHAR NOT NULL, + PRIMARY KEY(stemmed_word,file_id, unstemmed_word), + FOREIGN KEY (stemmed_word, file_id) REFERENCES termvectors (wordroot, file_id) + ON UPDATE CASCADE ON DELETE CASCADE +); +CREATE INDEX i_unstemmed_words_stemmed_word ON unstemmed_words (stemmed_word); + + +/* + * CRIMSy development + */ +CREATE TABLE projecttypes ( + id INTEGER NOT NULL PRIMARY KEY, + type VARCHAR +); + +CREATE TABLE budgetreservationtypes ( + id INTEGER NOT NULL PRIMARY KEY +); + +CREATE TABLE materialtypes ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL +); + +CREATE TABLE materialdetailtypes ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL +); + +CREATE TABLE materialinformations ( + id INTEGER NOT NULL PRIMARY KEY, + materialtypeid INTEGER NOT NULL REFERENCES materialtypes(id), + materialdetailtypeid INTEGER NOT NULL REFERENCES materialdetailtypes(id), + mandatory BOOLEAN NOT NULL +); + +CREATE TABLE projects ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + budget NUMERIC, + budgetBlocked BOOLEAN default false, + projecttypeid INTEGER NOT NULL REFERENCES projecttypes(id), + owner_id INTEGER NOT NULL REFERENCES usersgroups(id), + aclist_id INTEGER NOT NULL REFERENCES aclists(id), + description VARCHAR, + ctime TIMESTAMP NOT NULL DEFAULT now(), + mtime TIMESTAMP NOT NULL DEFAULT now(), + deactivated BOOLEAN NOT NULL DEFAULT false +); +CREATE UNIQUE index project_index_name_unique ON projects (LOWER(name)); + +CREATE TABLE projecttemplates ( + id SERIAL NOT NULL PRIMARY KEY, + materialdetailtypeid INTEGER NOT NULL REFERENCES materialdetailtypes(id), + aclistid INTEGER NOT NULL REFERENCES aclists(id), + projectid INTEGER NOT NULL REFERENCES projects(id) +); + +CREATE TABLE budgetreservations ( + id SERIAL NOT NULL PRIMARY KEY, + startDate DATE, + endDate DATE , + type INTEGER NOT NULL REFERENCES budgetreservationtypes(id), + budget NUMERIC NOT NULL +); + + +INSERT INTO materialtypes VALUES(1,'STRUCTURE'); +INSERT INTO materialtypes VALUES(2,'MATERIAL_COMPOSITION'); +INSERT INTO materialtypes VALUES(3,'BIOMATERIAL'); +INSERT INTO materialtypes VALUES(4,'CONSUMABLE'); +INSERT INTO materialtypes VALUES(5,'SEQUENCE'); +INSERT INTO materialtypes VALUES(6,'TISSUE'); +INSERT INTO materialtypes VALUES(7,'TAXONOMY'); + + +INSERT INTO projecttypes VALUES(1,'CHEMICAL_PROJECT'); +INSERT INTO projecttypes VALUES(2,'IT_PROJECT'); +INSERT INTO projecttypes VALUES(3,'FINANCE_PROJECT'); +INSERT INTO projecttypes VALUES(4,'BIOLOGICAL_PROJECT'); +INSERT INTO projecttypes VALUES(5,'BIOCHEMICAL_PROJECT'); + + + +INSERT INTO materialdetailtypes VALUES(1,'COMMON_INFORMATION'); +INSERT INTO materialdetailtypes VALUES(2,'STRUCTURE_INFORMATION'); +INSERT INTO materialdetailtypes VALUES(3,'INDEX'); +INSERT INTO materialdetailtypes VALUES(4,'HAZARD_INFORMATION'); +INSERT INTO materialdetailtypes VALUES(5,'STORAGE_CLASSES'); +INSERT INTO materialdetailtypes VALUES(6,'TAXONOMY'); + +INSERT INTO materialInformations VALUES(1,1,1,false); +INSERT INTO materialInformations VALUES(2,1,2,false); +INSERT INTO materialInformations VALUES(3,1,3,false); +INSERT INTO materialInformations VALUES(4,1,4,false); +INSERT INTO materialInformations VALUES(5,1,5,false); + +INSERT INTO materialInformations VALUES(6,2,1,false); + +INSERT INTO materialInformations VALUES(7,3,1,false); +INSERT INTO materialInformations VALUES(8,3,6,false); + +INSERT INTO materialInformations VALUES(9,4,1,false); +INSERT INTO materialInformations VALUES(10,4,3,false); + + + +CREATE TABLE indextypes ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + javaclass VARCHAR, + UNIQUE(name)); + +CREATE TABLE materials ( + materialid SERIAL NOT NULL PRIMARY KEY, + materialTypeId INTEGER NOT NULL REFERENCES materialtypes(id), + ctime TIMESTAMP NOT NULL DEFAULT now(), + aclist_id INTEGER NOT NULL REFERENCES aclists(id), + owner_id INTEGER NOT NULL REFERENCES usersgroups(id), + deactivated BOOLEAN NOT NULL DEFAULT false, + projectId INTEGER REFERENCES projects(id)); + +CREATE TABLE material_indices ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + typeid INTEGER NOT NULL REFERENCES indextypes(id), + value VARCHAR NOT NULL, + language VARCHAR, + rank INTEGER); + +CREATE TABLE materialdetailrights ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + aclistid INTEGER NOT NULL REFERENCES aclists(id), + materialtypeid INTEGER NOT NULL REFERENCES materialdetailtypes(id)); + +CREATE TABLE molecules ( + id SERIAL NOT NULL PRIMARY KEY, + molecule TEXT); + +CREATE INDEX i_molecules_mol_idx ON molecules USING bingo_idx (molecule bingo.molecule); + +CREATE FUNCTION substructure (VARCHAR, VARCHAR) RETURNS BOOLEAN + AS $$ + -- + -- return true if the second structure is a substructure of the first. + -- + DECLARE + molecule ALIAS FOR $1; + substruct ALIAS FOR $2; + BEGIN + RETURN molecule @ ( substruct, '')::bingo.sub; + END; + $$ LANGUAGE plpgsql; + + +CREATE TABLE structures ( + id INTEGER PRIMARY KEY REFERENCES materials(materialid), + sumformula VARCHAR, + molarMass FLOAT, + exactMolarMass FLOAT, + moleculeID INTEGER REFERENCES molecules(id)); + +CREATE TABLE structures_hist ( + id INTEGER NOT NULL REFERENCES structures(id), + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + mtime TIMESTAMP NOT NULL, + digest VARCHAR, + sumformula_old VARCHAR, + sumformula_new VARCHAR, + molarMass_old FLOAT, + molarMass_new FLOAT, + exactMolarMass_old FLOAT, + exactMolarMass_new FLOAT, + moleculeId_old INTEGER REFERENCES molecules(id), + moleculeId_new INTEGER REFERENCES molecules(id), + PRIMARY KEY (id,mtime)); + +CREATE TABLE hazards ( + id INTEGER PRIMARY KEY, + name VARCHAR NOT NULL, + category INTEGER NOT NULL, + has_remarks BOOLEAN NOT NULL DEFAULT false); + +CREATE TABLE material_hazards ( + typeid INTEGER NOT NULL REFERENCES hazards(id), + materialid INTEGER NOT NULL REFERENCES materials(materialid), + remarks VARCHAR, + PRIMARY KEY(materialid,typeid)); + +CREATE TABLE storageclasses ( + id INTEGER PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE storageconditions ( + id INTEGER PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE storages ( + materialid INTEGER PRIMARY KEY REFERENCES materials(materialid), + storageclass INTEGER NOT NULL REFERENCES storageClasses(id), + description VARCHAR); + + +CREATE TABLE storageconditions_material ( + conditionId INTEGER NOT NULL REFERENCES storageconditions(id), + materialid INTEGER NOT NULL REFERENCES materials(materialid), + PRIMARY KEY (conditionId,materialid) +); + +insert into storageconditions(id,name)values(1,'moistureSensitive'); +insert into storageconditions(id,name)values(2,'keepMoisture'); +insert into storageconditions(id,name)values(3,'lightSensitive'); +insert into storageconditions(id,name)values(4,'underInertGas'); +insert into storageconditions(id,name)values(5,'acidSensitive'); +insert into storageconditions(id,name)values(6,'alkaliSensitive'); +insert into storageconditions(id,name)values(7,'KeepAwayFromOxidants'); +insert into storageconditions(id,name)values(8,'frostSensitive'); +insert into storageconditions(id,name)values(9,'keepCool'); +insert into storageconditions(id,name)values(10,'keepFrozen'); +insert into storageconditions(id,name)values(11,'storeUnderMinus40Degrees'); +insert into storageconditions(id,name)values(12,'storeUnderMinus80Degrees'); + +insert into indextypes(name,javaclass)values('name',null); +insert into indextypes(name,javaclass)values('GESTIS/ZVG',null); +insert into indextypes(name,javaclass)values('CAS/RN',null); +insert into indextypes(name,javaclass)values('Carl Roth Sicherheitsdatenblatt',null); + +insert into storageclasses(id,name)values(1,'1'); +insert into storageclasses(id,name)values(2,'2A'); +insert into storageclasses(id,name)values(3,'2B'); +insert into storageclasses(id,name)values(4,'3'); +insert into storageclasses(id,name)values(5,'4.1A'); +insert into storageclasses(id,name)values(6,'4.1B'); +insert into storageclasses(id,name)values(7,'4.2'); +insert into storageclasses(id,name)values(8,'4.3'); +insert into storageclasses(id,name)values(9,'5.1A'); +insert into storageclasses(id,name)values(10,'5.1B'); +insert into storageclasses(id,name)values(11,'5.1C'); +insert into storageclasses(id,name)values(12,'6.1A'); +insert into storageclasses(id,name)values(13,'6.1B'); +insert into storageclasses(id,name)values(14,'6.1C'); +insert into storageclasses(id,name)values(15,'6.1D'); +insert into storageclasses(id,name)values(16,'6.2'); +insert into storageclasses(id,name)values(17,'7'); +insert into storageclasses(id,name)values(18,'8A'); +insert into storageclasses(id,name)values(19,'8B'); +insert into storageclasses(id,name)values(20,'10'); +insert into storageclasses(id,name)values(21,'11'); +insert into storageclasses(id,name)values(22,'12'); +insert into storageclasses(id,name)values(23,'13'); + +insert into hazards(id,name,category,has_remarks)values(1,'GHS01',1,false); +insert into hazards(id,name,category,has_remarks)values(2,'GHS02',1,false); +insert into hazards(id,name,category,has_remarks)values(3,'GHS03',1,false); +insert into hazards(id,name,category,has_remarks)values(4,'GHS04',1,false); +insert into hazards(id,name,category,has_remarks)values(5,'GHS05',1,false); +insert into hazards(id,name,category,has_remarks)values(6,'GHS06',1,false); +insert into hazards(id,name,category,has_remarks)values(7,'GHS07',1,false); +insert into hazards(id,name,category,has_remarks)values(8,'GHS08',1,false); +insert into hazards(id,name,category,has_remarks)values(9,'GHS09',1,false); +insert into hazards(id,name,category,has_remarks)values(10,'HS',2,true); +insert into hazards(id,name,category,has_remarks)values(11,'PS',2,true); +insert into hazards(id,name,category,has_remarks)values(12,'S1',3,false); +insert into hazards(id,name,category,has_remarks)values(13,'S2',3,false); +insert into hazards(id,name,category,has_remarks)values(14,'S3',3,false); +insert into hazards(id,name,category,has_remarks)values(15,'S4',3,false); +insert into hazards(id,name,category,has_remarks)values(16,'R1',4,false); +insert into hazards(id,name,category,has_remarks)values(17,'C1',5,true); +insert into hazards(id,name,category,has_remarks)values(18,'GHS10',1,false); +insert into hazards(id,name,category,has_remarks)values(19,'GHS11',1,false); +insert into hazards(id,name,category,has_remarks)values(20,'GMO',6,false); + +CREATE TABLE materials_hist ( + materialid INTEGER NOT NULL REFERENCES materials(materialid), + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + mDate TIMESTAMP, + digest VARCHAR, + action VARCHAR, + aclistid_old INTEGER REFERENCES aclists(id), + aclistid_new INTEGER REFERENCES aclists(id), + projectid_old INTEGER REFERENCES projects(id), + projectid_new INTEGER REFERENCES projects(id), + ownerid_old INTEGER REFERENCES usersgroups(id), + ownerid_new INTEGER REFERENCES usersgroups(id), + PRIMARY KEY(materialid,mDate)); + +CREATE TABLE material_indices_hist ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + typeid INTEGER NOT NULL REFERENCES indextypes(id), + mDate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + value_old VARCHAR, + value_new VARCHAR, + rank_old INTEGER, + rank_new INTEGER, + language_old VARCHAR, + language_new VARCHAR); + +CREATE TABLE material_hazards_hist ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + mdate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + typeid_old INTEGER REFERENCES hazards(id), + typeid_new INTEGER REFERENCES hazards(id), + remarks_old VARCHAR, + remarks_new VARCHAR); + +CREATE TABLE storages_hist ( + materialid INTEGER NOT NULL REFERENCES materials(materialid), + mdate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + description_old VARCHAR, + description_new VARCHAR, + storageclass_old INTEGER REFERENCES storageclasses(id), + storageclass_new INTEGER REFERENCES storageclasses(id), + PRIMARY KEY(materialid,mdate)); + + +CREATE TABLE storagesconditions_storages_hist ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + mdate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + conditionId_old INTEGER, + conditionId_new INTEGER); + +/* + * transportable: container can change place (ie change the parent in the hierarchy) + * uniq_name: multiple containers can share the same name if they have different parents + */ +CREATE TABLE containertypes( + name varchar NOT NULL PRIMARY KEY, + description varchar, + transportable BOOLEAN NOT NULL DEFAULT true, + unique_name BOOLEAN NOT NULL DEFAULT true, + rank integer not null); + +CREATE TABLE containers( + id SERIAL NOT NULL PRIMARY KEY, + parentcontainer INTEGER REFERENCES containers(id), + label VARCHAR NOT NULL, + projectid INTEGER REFERENCES projects(id), + rows INTEGER, + columns INTEGER, + type VARCHAR NOT NULL REFERENCES containertypes(name), + firearea VARCHAR, + gmosafetylevel VARCHAR, + barcode VARCHAR, + swapdimensions BOOLEAN NOT NULL DEFAULT FALSE, + zerobased BOOLEAN NOT NULL DEFAULT FALSE, + deactivated BOOLEAN NOT NULL DEFAULT false); + +CREATE TABLE nested_containers( + sourceid INTEGER NOT NULL REFERENCES containers(id), + targetid INTEGER NOT NULL REFERENCES containers(id), + nested BOOLEAN NOT NULL, + PRIMARY KEY(sourceid,targetid)); + +CREATE TABLE solvents( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL); + + +CREATE TABLE items( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + amount FLOAT NOT NULL, + articleid INTEGER, + projectid INTEGER REFERENCES projects(id), + concentration FLOAT, + concentrationunit VARCHAR, + unit VARCHAR, + purity VARCHAR, + solventid INTEGER REFERENCES solvents(id), + description VARCHAR, + owner_id INTEGER NOT NULL REFERENCES usersgroups(id), + containersize FLOAT, + containertype VARCHAR REFERENCES containertypes(name), + containerid INTEGER REFERENCES containers(id), + ctime TIMESTAMP NOT NULL DEFAULT now(), + expiry_date TIMESTAMP, + aclist_id INTEGER NOT NULL, + label VARCHAR, + parent_id INTEGER REFERENCES items(id) +); + +CREATE TABLE item_positions( + id SERIAL NOT NULL PRIMARY KEY, + itemid INTEGER NOT NULL REFERENCES items(id) ON UPDATE CASCADE ON DELETE CASCADE, + containerid INTEGER NOT NULL REFERENCES containers(id) ON UPDATE CASCADE ON DELETE CASCADE, + itemrow INTEGER, + itemcol INTEGER, + UNIQUE(itemid), + UNIQUE(containerid, itemrow, itemcol) +); + +CREATE TABLE items_history( + itemid INTEGER NOT NULL REFERENCES items(id), + mdate TIMESTAMP NOT NULL, + actorid INTEGER NOT NULL REFERENCES usersgroups(id), + action VARCHAR NOT NULL, + projectid_old INTEGER REFERENCES projects(id), + projectid_new INTEGER REFERENCES projects(id), + concentration_old FLOAT, + concentration_new FLOAT, + purity_old VARCHAR, + purity_new VARCHAR, + description_new VARCHAR, + description_old VARCHAR, + amount_old FLOAT, + amount_new FLOAT, + owner_old INTEGER REFERENCES usersgroups(id), + owner_new INTEGER REFERENCES usersgroups(id), + parent_containerid_new INTEGER REFERENCES containers(id), + parent_containerid_old INTEGER REFERENCES containers(id), + aclistid_old INTEGER REFERENCES aclists(id), + aclistid_new INTEGER REFERENCES aclists(id), + PRIMARY KEY(itemid,actorid,mdate)); + +CREATE TABLE item_positions_history( + id SERIAL PRIMARY KEY, + itemid INTEGER NOT NULL REFERENCES items(id), + containerid INTEGER NOT NULL REFERENCES containers(id), + mdate TIMESTAMP NOT NULL, + actorid INTEGER NOT NULL REFERENCES usersgroups(id), + row_old INTEGER, + row_new INTEGER, + col_old INTEGER, + col_new INTEGER +); + +insert into containertypes(name,description,rank,transportable,unique_name)values('ROOM',null,100,false,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('CUPBOARD',null,90,false,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('FREEZER',null,90,false,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('FRIDGE',null,90,false,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('TRAY',null,60,true,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('WELLPLATE',null,50,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('GLASS_BOTTLE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_BOTTLE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('GLASS_VIAL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_VIAL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('GLASS_AMPOULE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_AMPOULE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('STEEL_BARREL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_BARREL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('STEEL_CONTAINER',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_CONTAINER',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('CARTON',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_BAG',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_SACK',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PAPER_BAG',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('COMPRESSED_GAS_CYLINDER',null,0,true,true); + +CREATE TABLE taxonomy_level( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + rank INTEGER NOT NULL); + +CREATE TABLE taxonomy( + id INTEGER NOT NULL PRIMARY KEY REFERENCES materials(materialid), + level INTEGER NOT NULL REFERENCES taxonomy_level(id)); + +CREATE TABLE effective_taxonomy( + id SERIAL NOT NULL PRIMARY KEY, + taxoid INTEGER NOT NULL REFERENCES taxonomy(id), + parentid INTEGER NOT NULL REFERENCES taxonomy(id)); + +CREATE TABLE taxonomy_history( + taxonomyid INTEGER NOT NULL REFERENCES materials(materialid), + actorid INTEGER NOT NULL REFERENCES usersgroups(id), + mdate TIMESTAMP NOT NULL, + action VARCHAR NOT NULL, + digest VARCHAR, + level_old INTEGER, + level_new INTEGER, + parentid_old INTEGER, + parentid_new INTEGER, + PRIMARY KEY(taxonomyid,actorid,mdate)); + +CREATE TABLE tissues( + id INTEGER NOT NULL PRIMARY KEY REFERENCES materials(materialid), + taxoid INTEGER NOT NULL REFERENCES taxonomy(id) +); + +CREATE TABLE biomaterial( + id INTEGER NOT NULL PRIMARY KEY REFERENCES materials(materialid), + taxoid INTEGER NOT NULL REFERENCES taxonomy(id), + tissueid INTEGER REFERENCES tissues(id) +); + +CREATE TABLE biomaterial_history( + id INTEGER NOT NULL REFERENCES biomaterial(id), + actorid INTEGER NOT NULL REFERENCES usersGroups(id), + mtime TIMESTAMP NOT NULL, + digest VARCHAR, + action VARCHAR NOT NULL, + tissueid_old INTEGER REFERENCES tissues(id), + tissueid_new INTEGER REFERENCES tissues(id), + taxoid_old INTEGER REFERENCES taxonomy(id), + taxoid_new INTEGER REFERENCES taxonomy(id), + PRIMARY KEY(id,actorid,mtime) +); + +INSERT INTO taxonomy_level VALUES(1,'domain',100); +INSERT INTO taxonomy_level VALUES(2,'kingdom',200); +INSERT INTO taxonomy_level VALUES(3,'subkingdom',300); +INSERT INTO taxonomy_level VALUES(4,'division',400); +INSERT INTO taxonomy_level VALUES(5,'subdivision',500); +INSERT INTO taxonomy_level VALUES(6,'class',600); +INSERT INTO taxonomy_level VALUES(7,'subclass',700); +INSERT INTO taxonomy_level VALUES(8,'superorder',800); +INSERT INTO taxonomy_level VALUES(9,'order',900); +INSERT INTO taxonomy_level VALUES(10,'suborder',1000); +INSERT INTO taxonomy_level VALUES(11,'family',1100); +INSERT INTO taxonomy_level VALUES(12,'subfamily',1200); +INSERT INTO taxonomy_level VALUES(13,'tribe',1300); +INSERT INTO taxonomy_level VALUES(14,'genus',1400); +INSERT INTO taxonomy_level VALUES(15,'section',1500); +INSERT INTO taxonomy_level VALUES(16,'series',1600); +INSERT INTO taxonomy_level VALUES(17,'aggregate',1700); +INSERT INTO taxonomy_level VALUES(18,'species',1800); +INSERT INTO taxonomy_level VALUES(19,'subspecies',1900); +INSERT INTO taxonomy_level VALUES(20,'variety',2000); +INSERT INTO taxonomy_level VALUES(21,'form',2100); + + + +/* + * ToDo: xxxxx add ON UPDATE CASCADE/ ON DELETE CASCADE ? + */ + +CREATE TABLE folders ( + folderid SERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + parentid INTEGER REFERENCES folders(folderid) ON UPDATE RESTRICT ON DELETE RESTRICT, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE RESTRICT ON DELETE RESTRICT, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE RESTRICT ON DELETE RESTRICT, + ctime TIMESTAMP NOT NULL DEFAULT now(), + projectid INTEGER REFERENCES projects(id) ON UPDATE RESTRICT ON DELETE RESTRICT +); +INSERT INTO folders (name) VALUES ('default'); + +CREATE TABLE experiments ( + experimentid SERIAL NOT NULL PRIMARY KEY, + code VARCHAR, + description VARCHAR, + template BOOLEAN NOT NULL DEFAULT FALSE, + folderid INTEGER NOT NULL REFERENCES folders(folderid) ON UPDATE RESTRICT ON DELETE RESTRICT, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + ctime TIMESTAMP NOT NULL DEFAULT now(), + projectid INTEGER REFERENCES projects(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE exp_records ( + exprecordid BIGSERIAL NOT NULL PRIMARY KEY, + experimentid INTEGER NOT NULL REFERENCES experiments (experimentid) ON UPDATE CASCADE ON DELETE CASCADE, + changetime TIMESTAMP, + creationtime TIMESTAMP, + type INTEGER, + revision INTEGER NOT NULL DEFAULT 1, + next BIGINT DEFAULT NULL REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE SET NULL +); + +/* + * Note: currently either materialid or itemid must be set + * + * ToDo: + * - add references to documents, users, etc. + * - additional indexing for payload column (works + * only if payload is of JSON type; see example below) + */ +CREATE TABLE linked_data ( + recordid BIGSERIAL NOT NULL PRIMARY KEY, + exprecordid BIGINT NOT NULL + REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + materialid INTEGER + REFERENCES materials(materialid) ON UPDATE CASCADE ON DELETE CASCADE, + itemid INTEGER CHECK (COALESCE(materialid, itemid,fileid) IS NOT NULL) + REFERENCES items(id) ON UPDATE CASCADE ON DELETE CASCADE, + fileid INTEGER REFERENCES files(id) ON UPDATE CASCADE ON DELETE CASCADE, + rank INTEGER DEFAULT 0, + type INTEGER NOT NULL, + payload VARCHAR +); + +/* + * B-tree index example: + * CREATE INDEX i_exp_linked_data_val ON exp_linked_data (((payload->>'val')::DOUBLE PRECISION)) + * WHERE (payload->>'val') IS NOT NULL; + */ + +CREATE TABLE exp_assays ( + exprecordid BIGINT NOT NULL PRIMARY KEY REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + outcometype INTEGER NOT NULL, + remarks VARCHAR, + units VARCHAR +); + +/* ToDo: xxxxx create fulltext index on exp_texts! */ +CREATE TABLE exp_texts ( + exprecordid BIGINT NOT NULL PRIMARY KEY REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + text VARCHAR +); + +/** + * - Agency: job scheduling + * - barcode printing + */ +CREATE TABLE jobs ( + jobid SERIAL NOT NULL PRIMARY KEY, + input BYTEA, + jobdate TIMESTAMP NOT NULL DEFAULT now(), + jobtype INTEGER NOT NULL, + output BYTEA, + ownerid INTEGER REFERENCES usersgroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + queue VARCHAR NOT NULL, + status INTEGER NOT NULL +); + +CREATE TABLE printers ( + queue VARCHAR NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + aclistid INTEGER NOT NULL REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + config VARCHAR NOT NULL DEFAULT '', + contact VARCHAR NOT NULL DEFAULT '', + driver VARCHAR NOT NULL, + model VARCHAR NOT NULL DEFAULT '', + ownerid INTEGER NOT NULL REFERENCES usersgroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + place VARCHAR NOT NULL DEFAULT '', + status INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE labels ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL DEFAULT '', + description VARCHAR NOT NULL DEFAULT '', + labeltype VARCHAR NOT NULL DEFAULT '', + printermodel VARCHAR NOT NULL DEFAULT '', + config VARCHAR NOT NULL DEFAULT '' +); + +CREATE TABLE preferences ( + id SERIAL NOT NULL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES usersgroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + key VARCHAR NOT NULL, + value VARCHAR, + UNIQUE (user_id, key) +); + +CREATE TABLE images ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + title VARCHAR, + preview VARCHAR, + image VARCHAR, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +/* + * finally set DBSchema Version + */ +UPDATE lbac.info SET value=:LBAC_SCHEMA_VERSION WHERE key='DBSchema Version'; +COMMIT; + diff --git a/tx/src/test/resources/test-persistence.xml b/tx/src/test/resources/test-persistence.xml index 718a9dd8a..d9d314cd5 100644 --- a/tx/src/test/resources/test-persistence.xml +++ b/tx/src/test/resources/test-persistence.xml @@ -37,12 +37,10 @@ - + - + + + 4.0.0 + + de.ipb-halle + kx-api + 1.0.0 + jar + + KX-API + http://github.com/ipb-halle/CRIMSy + + + UTF-8 + + + 5.3.26.Final + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + + + + + + + make-assembly + package + + single + + + + + + + + + + + + + de.ipb-halle + crimsy-api + 1.0.0 + + + + + junit + junit + 4.13.1 + test + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + test + + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-ehcache + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-core + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-validator + 5.3.6.Final + provided + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + org.apache.tomee + javaee-api + 7.0-2 + provided + + + + + org.postgresql + postgresql + 42.4.3 + + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + test + + + org.testcontainers + postgresql + 1.16.3 + test + + + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha6 + test + + + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.6 + pom + test + + + + + org.apache.tomee + arquillian-tomee-embedded + 7.0.9 + test + + + + org.awaitility + awaitility + 4.0.3 + test + + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + commons-io + commons-io + + + + + + diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/AttachmentHolder.java b/kx-api/src/main/java/de/ipb_halle/kx/file/AttachmentHolder.java similarity index 96% rename from tx-api/src/main/java/de/ipb_halle/tx/file/AttachmentHolder.java rename to kx-api/src/main/java/de/ipb_halle/kx/file/AttachmentHolder.java index 9b87957db..22eff3b66 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/AttachmentHolder.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/AttachmentHolder.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.file; /** * diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/FileObject.java b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObject.java similarity index 86% rename from tx-api/src/main/java/de/ipb_halle/tx/file/FileObject.java rename to kx-api/src/main/java/de/ipb_halle/kx/file/FileObject.java index dc2a697f2..2244b166b 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/FileObject.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObject.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.file; import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; @@ -32,14 +32,14 @@ public class FileObject implements Serializable, DTO { private Date created; private Integer userId; private Integer collectionId; - private String document_language; + private String documentLanguage; /** * default constructor */ public FileObject() { created = new Date(); - document_language = "en"; + documentLanguage = "en"; } /** @@ -53,7 +53,7 @@ public FileObject(FileObjectEntity entity) { this.fileLocation = entity.getFilename(); this.hash = entity.getHash(); this.created = entity.getCreated(); - this.document_language = entity.getDocument_language(); + this.documentLanguage = entity.getDocumentLanguage(); this.collectionId = entity.getCollectionId(); this.userId = entity.getUserId(); } @@ -63,7 +63,7 @@ public FileObjectEntity createEntity() { return new FileObjectEntity() .setCollectionId(collectionId) .setCreatedFromDate(created) - .setDocument_language(document_language) + .setDocumentLanguage(documentLanguage) .setFilename(fileLocation) .setHash(hash) .setName(name) @@ -134,12 +134,12 @@ public void setUserId(Integer uid) { this.userId = uid; } - public String getDocument_language() { - return document_language; + public String getDocumentLanguage() { + return documentLanguage; } - public void setDocument_language(String document_language) { - this.document_language = document_language; + public void setDocumentLanguage(String lang) { + this.documentLanguage = lang; } } diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectEntity.java b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectEntity.java similarity index 93% rename from tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectEntity.java rename to kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectEntity.java index 05dfe9525..b897488c8 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectEntity.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectEntity.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.file; import de.ipb_halle.crimsy_api.AttributeTag; import de.ipb_halle.crimsy_api.AttributeType; @@ -94,7 +94,7 @@ public Integer getCollectionId() { return collectionId; } - public String getDocument_language() { + public String getDocumentLanguage() { return document_language; } @@ -133,8 +133,8 @@ public FileObjectEntity setName(String name) { return this; } - public FileObjectEntity setDocument_language(String document_language) { - this.document_language = document_language; + public FileObjectEntity setDocumentLanguage(String lang) { + this.document_language = lang; return this; } diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectList.java similarity index 97% rename from tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java rename to kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectList.java index bd88da52b..6fcd99b4d 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/FileObjectList.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectList.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.file; import java.io.Serializable; import java.util.List; diff --git a/tx/src/main/java/de/ipb_halle/tx/service/FileObjectService.java b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java similarity index 79% rename from tx/src/main/java/de/ipb_halle/tx/service/FileObjectService.java rename to kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java index 9be0a32e4..db4248855 100644 --- a/tx/src/main/java/de/ipb_halle/tx/service/FileObjectService.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java @@ -15,18 +15,20 @@ * limitations under the License. * */ -package de.ipb_halle.tx.service; +package de.ipb_halle.kx.file; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.tx.file.FileObjectEntity; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectEntity; import java.io.Serializable; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Stateless public class FileObjectService implements Serializable { @@ -36,7 +38,7 @@ public class FileObjectService implements Serializable { @PersistenceContext(name = "de.ipb_halle.lbac") private EntityManager em; - private final Logger logger = LogManager.getLogger(this.getClass().getName()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * get file entity by id @@ -51,4 +53,8 @@ public FileObject loadFileObjectById(Integer id) { } return null; } + + public FileObject save(FileObject f) { + return new FileObject(this.em.merge(f.createEntity())); + } } diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/UnsupportedLanguageException.java b/kx-api/src/main/java/de/ipb_halle/kx/file/UnsupportedLanguageException.java similarity index 97% rename from tx-api/src/main/java/de/ipb_halle/tx/file/UnsupportedLanguageException.java rename to kx-api/src/main/java/de/ipb_halle/kx/file/UnsupportedLanguageException.java index c0fea9ac1..a065b2800 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/UnsupportedLanguageException.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/UnsupportedLanguageException.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.file; /** * diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/StemmedWordOrigin.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java similarity index 60% rename from tx-api/src/main/java/de/ipb_halle/tx/file/StemmedWordOrigin.java rename to kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java index fdc7580f1..67b9d7f34 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/StemmedWordOrigin.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java @@ -15,23 +15,21 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; - -import java.util.HashSet; -import java.util.Set; +package de.ipb_halle.kx.termvector; /** * + * @see TermVectorEntity for SqlResultSetMapping * @author fmauz */ public class StemmedWordOrigin { private String stemmedWord; - private Set originalWord = new HashSet<>(); + private String originalWord; - public StemmedWordOrigin(String stemmedWord, Set originalWords) { - this.stemmedWord = stemmedWord; - this.originalWord = originalWords; + public StemmedWordOrigin(String wordroot, String original) { + this.stemmedWord = wordroot; + this.originalWord = original; } public StemmedWordOrigin() { @@ -41,20 +39,16 @@ public String getStemmedWord() { return stemmedWord; } - public void setStemmedWord(String stemmedWord) { - this.stemmedWord = stemmedWord; + public void setStemmedWord(String s) { + this.stemmedWord = s; } - public Set getOriginalWord() { + public String getOriginalWord() { return originalWord; } - public void addOriginWord(String word) { - originalWord.add(word); - } - - public void setOriginalWord(Set originalWord) { - this.originalWord = originalWord; + public void setOriginalWord(String o) { + this.originalWord = o; } } diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequency.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermFrequency.java similarity index 93% rename from tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequency.java rename to kx-api/src/main/java/de/ipb_halle/kx/termvector/TermFrequency.java index dfa06b789..fd972226a 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequency.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermFrequency.java @@ -15,12 +15,13 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.termvector; /** * Stores a stemmed word with its frequency in a corpus. * * @author fmauz + * @see TermVectorEntity for SqlResultSetMapping */ public class TermFrequency { diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequencyList.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermFrequencyList.java similarity index 91% rename from tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequencyList.java rename to kx-api/src/main/java/de/ipb_halle/kx/termvector/TermFrequencyList.java index ee4ba4110..6c4347e5d 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/TermFrequencyList.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermFrequencyList.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.termvector; import java.util.ArrayList; import java.util.List; @@ -42,6 +42,14 @@ public TermFrequencyList(Map wordsAndFreq) { } } + /** + * constructor for SQL mapped results (@see TermVectorEntity) + * @param tf list of TermFrequency objects in descending order + */ + public TermFrequencyList(List tf) { + termFreq = tf; + } + public TermFrequencyList() { } @@ -105,7 +113,7 @@ public String getWordRepresantationOf(String wordRoot) { for (StemmedWordOrigin swo : unstemmedWords) { if (swo.getStemmedWord().equals(wordRoot)) { - return swo.getOriginalWord().iterator().next(); + return swo.getOriginalWord(); } } return wordRoot; @@ -120,10 +128,8 @@ public String getWordRepresantationOf(String wordRoot) { */ public String getStemmedWordFor(String d) { for (StemmedWordOrigin swo : unstemmedWords) { - for (String s : swo.getOriginalWord()) { - if (s.equals(d)) { - return swo.getStemmedWord(); - } + if (swo.getOriginalWord().equals(d)) { + return swo.getStemmedWord(); } } return null; diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/TermVector.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVector.java similarity index 98% rename from tx-api/src/main/java/de/ipb_halle/tx/file/TermVector.java rename to kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVector.java index a37fe4461..e3c19b9c9 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/TermVector.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVector.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.termvector; import de.ipb_halle.crimsy_api.DTO; import java.io.Serializable; diff --git a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorEntity.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorEntity.java new file mode 100644 index 000000000..15f4fb116 --- /dev/null +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorEntity.java @@ -0,0 +1,110 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.termvector; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.ColumnResult; +import javax.persistence.ConstructorResult; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.NamedNativeQuery; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.Table; + +/** + * + * @author fmauz + */ +@Entity +@Table(name = "termvectors") +@SqlResultSetMapping(name="TermFrequencyResult", + classes = { + @ConstructorResult( + columns = { + @ColumnResult(name="wordroot"), + @ColumnResult(name="frequency", type=Integer.class)}, + targetClass = TermFrequency.class) + } +) +@SqlResultSetMapping(name="StemmedWordOriginResult", classes = { + @ConstructorResult(targetClass = StemmedWordOrigin.class, + columns = {@ColumnResult(name="wordroot"), @ColumnResult(name="original")}) +}) +@NamedNativeQuery( + name="SQL_LOAD_TERMFREQUENCY", + query="SELECT wordroot, termfrequency AS frequency FROM termvectors WHERE file_id=:fileId ORDER BY frequency DESC", + resultSetMapping="TermFrequencyResult") + +@NamedNativeQuery( + name="SQL_LOAD_TERMFREQUENCY_BY_WORD", + query="SELECT wordroot, termfrequency AS frequency FROM termvectors WHERE file_id=(:fileId) AND wordroot in(:words) ORDER BY frequency DESC", + resultSetMapping="TermFrequencyResult") + +@NamedNativeQuery( + name="SQL_LOAD_TERMFREQUENCIES", + query="SELECT wordroot, SUM(termfrequency) AS frequency FROM termvectors WHERE file_id in (:fileIds) GROUP BY wordroot ORDER BY frequency DESC", + resultSetMapping="TermFrequencyResult") + +@NamedNativeQuery( + name="SQL_LOAD_UNSTEMMED_WORD", + query="SELECT stemmed_word AS wordroot, unstemmed_word AS original FROM unstemmed_words WHERE file_id=:fileId AND stemmed_word=:wordroot", + resultSetMapping="StemmedWordOriginResult") + +@NamedNativeQuery( + name="SQL_LOAD_UNSTEMMED_WORDS", + query="SELECT stemmed_word AS wordroot, unstemmed_word AS original FROM unstemmed_words WHERE file_id=:fileId AND stemmed_word IN (:wordroot)", + resultSetMapping="StemmedWordOriginResult") + +public class TermVectorEntity implements Serializable { + + public final static String SQL_LOAD_TERMFREQUENCY="SQL_LOAD_TERMFREQUENCY"; + public final static String SQL_LOAD_TERMFREQUENCY_BY_WORD="SQL_LOAD_TERMFREQUENCY_BY_WORD"; + public final static String SQL_LOAD_TERMFREQUENCIES="SQL_LOAD_TERMFREQUENCIES"; + public final static String SQL_LOAD_UNSTEMMED_WORD="SQL_LOAD_UNSTEMMED_WORD"; + public final static String SQL_LOAD_UNSTEMMED_WORDS="SQL_LOAD_UNSTEMMED_WORDS"; + + + @EmbeddedId + private TermVectorId id; + + @Column(name = "termfrequency") + private int termFrequency; + + public TermVectorEntity() { + } + + public TermVectorId getId() { + return id; + } + + public int getTermFrequency() { + return termFrequency; + } + + public TermVectorEntity setId(TermVectorId id) { + this.id = id; + return this; + } + + public TermVectorEntity setTermFrequency(int termFrequency) { + this.termFrequency = termFrequency; + return this; + } + +} diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorId.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorId.java similarity index 98% rename from tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorId.java rename to kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorId.java index a4ce6f5cb..3a3f3c57b 100644 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorId.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorId.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.file; +package de.ipb_halle.kx.termvector; import de.ipb_halle.crimsy_api.AttributeTag; import de.ipb_halle.crimsy_api.AttributeType; diff --git a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java new file mode 100644 index 000000000..4f9d4d54d --- /dev/null +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java @@ -0,0 +1,235 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.termvector; + +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; +/* +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermFrequency; +import de.ipb_halle.kx.termvector.TermVectorEntity; +*/ + +import java.io.Serializable; +import java.util.ArrayList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.PostConstruct; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Stateless +public class TermVectorService implements Serializable { + + protected final String SQL_TOTAL_WORD_COUNT + = "SELECT SUM(t.termfrequency) FROM termvectors t"; + + protected final String SQL_DELETE_UNSTEMMED_WORDS_BY_ID + = "DELETE FROM unstemmed_words tv WHERE tv.file_id in(:fileId)"; + + protected final String SQL_DELETE_TERMVECTOR_BY_ID + = "DELETE FROM termvectors tv WHERE tv.file_id in(:fileId)"; + + protected final String SQL_INSERT_UNSTEMMED_WORD + = "INSERT INTO unstemmed_words (unstemmed_word, file_id, stemmed_word)" + + " VALUES (:unstemmed_word,:fileId,:stemmed_word)"; + + protected final String SQL_DELETE_ALL_UNSTEMMED_WORDS + = "DELETE FROM unstemmed_words"; + + protected final String SQL_DELETE_ALL_TERMVECTORS + = "DELETE from termvectors"; + + + + @PersistenceContext(name = "de.ipb_halle.lbac") + public EntityManager em; + + private Logger logger; + + /** + * init. check injection points + */ + @PostConstruct + private void init() { + logger = LoggerFactory.getLogger(this.getClass()); + if (em == null) { + logger.error("Injection Entitymanager failed."); + } + } + + /** + * getTermvector with aggregation, grouping and order result. Uses the + * TermVectorEntity.SQL_LOAD_TERMFREQUENCIES native query. + * + * @param fileId - document ids + * @param maxResult - return top max. rows for result set + * @return list of TermFrequency objects, ordered by descending frequency + */ + @SuppressWarnings("unchecked") + public List getTermVector(List fileIds, Integer maxResult) { + try { + if (fileIds.isEmpty()) { + return new ArrayList<>(); + } + + return this.em.createNamedQuery( + TermVectorEntity.SQL_LOAD_TERMFREQUENCIES) + .setMaxResults(maxResult) + .setParameter("fileIds", fileIds) + .getResultList(); + + } catch (Exception e) { + logger.error("getTermVector() caught an Exception: ", (Throwable) e); + return new ArrayList<>(); + } + } + + /** + * Get term frequencies with result ordering for a specific file and + * a set of words. Uses the TermVectorEntity.SQL_LOAD_TERMFREQUENCY_BY_WORD native query. + * + * @param fileId document ids + * @param words set of words, the document should match + * @return list of TermFrequency objects, ordered by descending frequency + */ + @SuppressWarnings("unchecked") + public List getTermFrequencies(Integer fileId, Set words) { + try { + return this.em.createNamedQuery( + TermVectorEntity.SQL_LOAD_TERMFREQUENCY_BY_WORD) + .setParameter("fileId", fileId) + .setParameter("words", words) + .getResultList(); + + } catch (Exception e) { + logger.error("getTermFrequencies() caught an Exception: ", (Throwable) e); + return new ArrayList<>(); + } + } + + + /** + * Sums all words from all documents of the local node + * + * @return + */ + public int getSumOfAllWordsFromAllDocs() { + java.math.BigInteger sum = (java.math.BigInteger) this.em.createNativeQuery(SQL_TOTAL_WORD_COUNT).getSingleResult(); + if (sum == null) { + return 0; + } else { + return sum.intValue(); + } + } + + public void deleteTermVector(FileObject fileObject) { + + this.em.createNativeQuery(SQL_DELETE_UNSTEMMED_WORDS_BY_ID) + .setParameter("fileId", fileObject.getId()) + .executeUpdate(); + + this.em.createNativeQuery(SQL_DELETE_TERMVECTOR_BY_ID) + .setParameter("fileId", fileObject.getId()) + .executeUpdate(); + + this.em.flush(); + } + + /** + * Saves the list of unstemmed words for a stemmed word of a file + * + * @param wordOrigins + * @param fileId + */ + public void saveUnstemmedWordsOfDocument( + List wordOrigins, + Integer fileId) { + for (StemmedWordOrigin swo : wordOrigins) { + this.em.createNativeQuery(SQL_INSERT_UNSTEMMED_WORD) + .setParameter("unstemmed_word", swo.getOriginalWord()) + .setParameter("fileId", fileId) + .setParameter("stemmed_word", swo.getStemmedWord()) + .executeUpdate(); + this.em.flush(); + } + } + + public void saveTermVectors(List vectors) { + try { + for(TermVector tv : vectors) { + this.em.merge(tv.createEntity()); + } + } catch (Exception e) { + logger.error("saveTermVectors() caught an exception: ", (Throwable) e); + } + } + + /** + * Loads the list of unstemmed words for a document and a stemmed word. + * + * @param fileId the file Id + * @param wordRoot a single wordRoot (= stem of a word) + * @return + */ + public List loadUnstemmedWordsOfDocument( + Integer fileId, + String wordRoot) { + + return this.em.createNamedQuery(TermVectorEntity.SQL_LOAD_UNSTEMMED_WORD) + .setParameter("fileId", fileId) + .setParameter("wordroot", wordRoot) + .getResultList(); + + } + + /** + * Loads the list of unstemmed words for a document and a stemmed word. + * + * @param fileId the file Id + * @param wordRoot a list of wordRoots (= stems of words) + * @return + */ + public List loadUnstemmedWordsOfDocument( + Integer fileId, + List wordRoot) { + + return this.em.createNamedQuery(TermVectorEntity.SQL_LOAD_UNSTEMMED_WORDS) + .setParameter("fileId", fileId) + .setParameter("wordroot", wordRoot) + .getResultList(); + } + + /** + * Deletes all termvectors and all unstemmed words from database. + */ + public void deleteTermVectors() { + this.em.createNativeQuery(SQL_DELETE_ALL_UNSTEMMED_WORDS).executeUpdate(); + this.em.createNativeQuery(SQL_DELETE_ALL_TERMVECTORS).executeUpdate(); + this.em.flush(); + } +} diff --git a/tx/src/test/java/de/ipb_halle/tx/service/FileObjectServiceTest.java b/kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java similarity index 95% rename from tx/src/test/java/de/ipb_halle/tx/service/FileObjectServiceTest.java rename to kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java index 647fe720a..8c4888e2f 100644 --- a/tx/src/test/java/de/ipb_halle/tx/service/FileObjectServiceTest.java +++ b/kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java @@ -3,11 +3,11 @@ * To change this template file, choose Tools | Templates * and open the template in the editor. */ -package de.ipb_halle.tx.service; +package de.ipb_halle.kx.file; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.testcontainers.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.tx.test.EntityManagerService; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.jboss.arquillian.container.test.api.Deployment; diff --git a/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java b/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java new file mode 100644 index 000000000..7d4d43157 --- /dev/null +++ b/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java @@ -0,0 +1,180 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.termvector; + +import de.ipb_halle.testcontainers.EntityManagerService; +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.termvector.TermVector; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import javax.inject.Inject; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.MethodOrderer.MethodName; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * + * @author fmauz + */ +@TestMethodOrder(MethodName.class) +@ExtendWith(PostgresqlContainerExtension.class) +@ExtendWith(ArquillianExtension.class) +public class TermVectorServiceTest { + + @Inject + private EntityManagerService ems; + + @Inject + private TermVectorService termVectorService; + + @Inject + private FileObjectService fileObjectService; + + + private Random random = new Random(); + + @Deployment + public static WebArchive createDeployment() { + System.setProperty("log4j.configurationFile", "log4j2-test.xml"); + + WebArchive archive = ShrinkWrap.create(WebArchive.class, "TermVectorServiceTest.war") + .addClass(FileObjectService.class) + .addClass(FileObject.class) + .addClass(TermVectorService.class) + .addClass(TermVector.class) + .addClass(EntityManagerService.class) + .addAsWebInfResource("test-persistence.xml", "persistence.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; + } + + @AfterEach + public void cleanUp() { + +// entityManagerService.getEntityManager() +// .createNativeQuery(String.format("DELETE FROM usersgroups WHERE name='%s'", user.getName())) +// .executeUpdate(); +// entityManagerService.getEntityManager() +// .createNativeQuery("DELETE FROM files") +// .executeUpdate(); + + } + + @Test + public void test001_termVectorService() { + + termVectorService.deleteTermVectors(); + + FileObject fE1 = createFileObject("en", "file1"); + FileObject fE2 = createFileObject("en", "file2"); + FileObject fE3 = createFileObject("de", "file3"); + FileObject fE4 = createFileObject("en", "file4"); + + fE1 = fileObjectService.save(fE1); + fE2 = fileObjectService.save(fE2); + fE3 = fileObjectService.save(fE3); + fE4 = fileObjectService.save(fE4); + + List vectors = new ArrayList<>(); + + vectors.add(new TermVector("testStemWord", fE1.getId(), 3)); + vectors.add(new TermVector("testStemWord2", fE1.getId(), 4)); + vectors.add(new TermVector("testStemWord", fE2.getId(), 5)); + vectors.add(new TermVector("testStemWord3", fE3.getId(), 7)); + vectors.add(new TermVector("testStemWord", fE4.getId(), 11)); + + termVectorService.saveTermVectors(vectors); + + List ids = new ArrayList<>(); + List results = termVectorService.getTermVector(ids, 10); + Assert.assertTrue("Result list for empty input is empty", results.isEmpty()); + + ids.add(fE1.getId()); + results = termVectorService.getTermVector(ids, 10); + Assert.assertEquals(2, results.size()); + int sum = 0; + sum = results.stream().map((i) -> i.getFrequency()).reduce(sum, Integer::sum); + Assert.assertEquals(7, sum); + + ids.add(fE2.getId()); + ids.add(fE3.getId()); + ids.add(fE4.getId()); + results = termVectorService.getTermVector(ids, 10); + Assert.assertEquals(3, results.size()); + sum = 0; + sum = results.stream().map((i) -> i.getFrequency()).reduce(sum, Integer::sum); + Assert.assertEquals(30, sum); + + results = termVectorService.getTermVector(ids, 2); + Assert.assertEquals(2, results.size()); + + Assert.assertEquals("Frequency of most frequent word", 19l, results.get(0).getFrequency().longValue()); + Assert.assertEquals("Term of most frequent word", "testStemWord", results.get(0).getTerm()); + Assert.assertEquals("Frequency of 2nd most frequent word", 7l, results.get(1).getFrequency().longValue()); + Assert.assertEquals("Term of 2nd most frequent word", "testStemWord3", results.get(1).getTerm()); + + int totalSum = termVectorService.getSumOfAllWordsFromAllDocs(); + Assert.assertTrue("Total number of words is >= 30", totalSum >= 30); + + termVectorService.deleteTermVector(fE4); + totalSum = termVectorService.getSumOfAllWordsFromAllDocs(); + Assert.assertEquals("Total number of words after deletion = 19", 19, totalSum); + + List wordOriginList = new ArrayList<> (); + wordOriginList.add(new StemmedWordOrigin("testStemWord", "originalWord1")); + wordOriginList.add(new StemmedWordOrigin("testStemWord", "originalWord2")); + wordOriginList.add(new StemmedWordOrigin("testStemWord", "originalWord3")); + + termVectorService.saveUnstemmedWordsOfDocument(wordOriginList, fE1.getId()); + + List words = termVectorService.loadUnstemmedWordsOfDocument(fE1.getId(), "testStemWord"); + Assert.assertEquals("Loading of unstemmed words not correct", 3, words.size()); + Assert.assertTrue(words.get(0).getStemmedWord().equals("testStemWord")); + Assert.assertTrue(words.get(0).getOriginalWord().startsWith("originalWord")); + + } + + private FileObject createFileObject(String language, String fileName) { + FileObject fO = new FileObject(); + fO.setCollectionId(null); + fO.setCreated(new Date()); + fO.setDocumentLanguage(language); + fO.setFileLocation(fileName); + fO.setHash(fileName); + fO.setName(fileName); + fO.setUserId(null); + return fO; + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/MessageConverter.java b/kx-api/src/test/java/de/ipb_halle/testcontainers/EntityManagerService.java similarity index 59% rename from ui/src/main/java/de/ipb_halle/lbac/message/MessageConverter.java rename to kx-api/src/test/java/de/ipb_halle/testcontainers/EntityManagerService.java index 437e8cc40..a895e9562 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/message/MessageConverter.java +++ b/kx-api/src/test/java/de/ipb_halle/testcontainers/EntityManagerService.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,23 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.message; +package de.ipb_halle.testcontainers; -import org.apache.johnzon.mapper.Converter; +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; -public class MessageConverter implements Converter { - @Override - public String toString(TermVectorSearchMessage termVectorSearchMessage) { - return null; - } +/** + * + * @author fbroda + */ +@Stateless +public class EntityManagerService { + + @PersistenceContext(name = "de.ipb_halle.lbac") + private EntityManager em; - @Override - public TermVectorSearchMessage fromString(String s) { - return null; + public EntityManager getEntityManager() { + return em; } } diff --git a/tx/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java b/kx-api/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java similarity index 100% rename from tx/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java rename to kx-api/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java diff --git a/tx/src/test/resources/arquillian.xml b/kx-api/src/test/resources/arquillian.xml similarity index 100% rename from tx/src/test/resources/arquillian.xml rename to kx-api/src/test/resources/arquillian.xml diff --git a/tx/src/test/resources/schema/00001.sql b/kx-api/src/test/resources/schema/00001.sql similarity index 99% rename from tx/src/test/resources/schema/00001.sql rename to kx-api/src/test/resources/schema/00001.sql index 5ae560912..7f044ca8e 100644 --- a/tx/src/test/resources/schema/00001.sql +++ b/kx-api/src/test/resources/schema/00001.sql @@ -21,7 +21,7 @@ * NOTE: TABLE CONSTRAINTS IN THIS FILE ARE RELAXED FOR TESTING PURPOSES: * * - collection_id may be NULL in table 'files' - * - + * - user_id may be NULL in table 'files' * *========================================================= * diff --git a/tx/src/test/resources/test-persistence.xml b/kx-api/src/test/resources/test-persistence.xml similarity index 94% rename from tx/src/test/resources/test-persistence.xml rename to kx-api/src/test/resources/test-persistence.xml index d9d314cd5..18d4a0e07 100644 --- a/tx/src/test/resources/test-persistence.xml +++ b/kx-api/src/test/resources/test-persistence.xml @@ -30,8 +30,8 @@ org.hibernate.jpa.HibernatePersistenceProvider - de.ipb_halle.tx.file.FileObjectEntity - de.ipb_halle.tx.file.TermVectorEntity + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity true diff --git a/kx-web/pom.xml b/kx-web/pom.xml new file mode 100644 index 000000000..248227057 --- /dev/null +++ b/kx-web/pom.xml @@ -0,0 +1,393 @@ + + + 4.0.0 + + de.ipb-halle + kx-web + 1.0.0 + + kx-web + http://www.ipb-halle.de + + + scm:git:https://github.com/ipb-halle/CRIMSy.git + https://github.com/ipb-halle/CRIMSy.git + + war + + + + + UTF-8 + + 1.28.3 + 5.3.26.Final + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + validate + + create + + + + + true + true + 8 + + {0} KX (git-sha1:{2} * {1,date,yyyy-MM-dd HH:mm:ss}) + + ${project.version} + timestamp + scmVersion + + + + + + + + + + + + maven-war-plugin + 2.4 + + web + true + ui + + + true + + + ${buildNumber} + + + + + + + + + + + + + + + de.ipb-halle + kx-api + 1.0.0 + + + + + commons-cli + commons-cli + 1.3.1 + + + + + org.apache.tomee + javaee-api + 7.0-2 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + + + org.apache.commons + commons-csv + 1.5 + + + + de.ipb-halle + kx-api + 1.0.0 + + + + de.ipb-halle + tx + 1.0.1 + + + org.apache.cxf + cxf-rt-rs-client + + + + + + + + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-ehcache + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-core + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-validator + 5.3.6.Final + provided + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + org.postgresql + postgresql + 42.4.3 + + + + + org.glassfish + javax.el + 3.0.1-b12 + test + + + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + test + + + org.testcontainers + postgresql + 1.16.3 + test + + + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha6 + test + + + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.6 + pom + test + + + + + org.apache.tomee + arquillian-tomee-embedded + 7.0.9 + test + + + + org.awaitility + awaitility + 4.0.3 + test + + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + commons-io + commons-io + + + + + + diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java similarity index 73% rename from ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java rename to kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java index 71418cc64..b6178450e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileAnalyser.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,12 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file.save; +package de.ipb_halle.kx.service; -import de.ipb_halle.tx.file.StemmedWordOrigin; -import de.ipb_halle.tx.file.TermVector; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.tx.text.LanguageDetectorFilter; import de.ipb_halle.tx.text.ParseTool; import de.ipb_halle.tx.text.TermVectorFilter; @@ -44,14 +46,13 @@ public class FileAnalyser { protected ParseTool parseTool = new ParseTool(); protected InputStream filterDefinition; - protected Integer fileId; private Logger logger = LogManager.getLogger(this.getClass()); public FileAnalyser(InputStream filterDefinition) { this.filterDefinition = filterDefinition; } - public String getLanguage() { + private String getLanguage() { @SuppressWarnings("unchecked") SortedSet languages = (SortedSet) this.parseTool .getFilterData() @@ -78,17 +79,14 @@ public String getLanguage() { return (maxLang != null) ? maxLang : "undefined"; } - public void analyseFile(String location, Integer fileId) throws FileNotFoundException { + protected void analyseFile(InputStream file){ parseTool.setFilterDefinition(filterDefinition); - parseTool.setInputStream(new FileInputStream( - new File(location))); + parseTool.setInputStream(file); parseTool.initFilter(); parseTool.parse(); - this.fileId = fileId; - } - public List getWordOrigins() { + protected List getWordOrigins() { List wordOrigins = new ArrayList<>(); @SuppressWarnings("unchecked") Map> map = (Map) parseTool.getFilterData().getValue(TermVectorFilter.STEM_DICT); @@ -98,7 +96,7 @@ public List getWordOrigins() { return wordOrigins; } - public List getTermVector() { + protected List getTermVector(Integer fileId) { List termVectors = new ArrayList<>(); @SuppressWarnings("unchecked") Map termvectorMap = (Map) parseTool.getFilterData().getValue(TermVectorFilter.TERM_VECTOR); @@ -107,4 +105,34 @@ public List getTermVector() { } return termVectors; } + + public void processFile(Integer fileId) { + try { + FileObject fileObj = fileObjectService.loadFileObjectById(fileId); + FileInputStream is = new FileInputStream(fileObj.getFileLocation()); + analyseFile(is); + saveResults(fileObj); + } catch (Exception e) { + + } + } + + private void saveResults(FileObject fileObj) { + saveTermVector(); + saveWordOrigins(); + updateLanguage(fileObj); + } + + private void saveTermVector() { + + } + + private void saveWordOrigins() { + + } + + private void updateLanguage(FileObject fileObj) { + fileObj.setDocumentLanguage(getLanguage()); + fileObjectService.save(fileObj); + } } diff --git a/tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java similarity index 92% rename from tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java rename to kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java index 6b0ce388e..348ed65fa 100644 --- a/tx/src/main/java/de/ipb_halle/tx/service/TextWebService.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.tx.service; +package de.ipb_halle.kx.service; import java.io.IOException; import java.io.PrintWriter; @@ -41,8 +41,9 @@ public class TextWebService extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { logger.info("doGet(): request received."); try { + final String fileId = req.getParameter("fileId"); final PrintWriter out = resp.getWriter(); - out.write("Success"); + out.write("Submitted: " + fileId); } catch (IOException e) { logger.error((Throwable) e); } diff --git a/tx/src/main/resources/log4j2.xml b/kx-web/src/main/resources/log4j2.xml similarity index 100% rename from tx/src/main/resources/log4j2.xml rename to kx-web/src/main/resources/log4j2.xml diff --git a/tx/src/main/webapp/WEB-INF/beans.xml b/kx-web/src/main/webapp/WEB-INF/beans.xml similarity index 100% rename from tx/src/main/webapp/WEB-INF/beans.xml rename to kx-web/src/main/webapp/WEB-INF/beans.xml diff --git a/tx/src/main/webapp/WEB-INF/web.xml b/kx-web/src/main/webapp/WEB-INF/web.xml similarity index 88% rename from tx/src/main/webapp/WEB-INF/web.xml rename to kx-web/src/main/webapp/WEB-INF/web.xml index 05a727317..94dc00359 100644 --- a/tx/src/main/webapp/WEB-INF/web.xml +++ b/kx-web/src/main/webapp/WEB-INF/web.xml @@ -18,13 +18,13 @@ --> - CRIMSy module for text / document processing and knowledge extraction + CRIMSy web service for text / document processing and knowledge extraction Knowledge Extractor Text Processing Service TextWebService - de.ipb_halle.tx.service.TextWebService + de.ipb_halle.kx.service.TextWebService 1 diff --git a/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java similarity index 75% rename from tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java rename to kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java index 43d13c894..0e7c2ec14 100644 --- a/tx/src/test/java/de/ipb_halle/tx/service/TextWebServiceTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java @@ -15,10 +15,13 @@ * limitations under the License. * */ -package de.ipb_halle.tx.service; +package de.ipb_halle.kx.service; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; import java.net.URL; import javax.inject.Inject; import org.apache.http.HttpResponse; @@ -48,14 +51,13 @@ @ExtendWith(PostgresqlContainerExtension.class) @ExtendWith(ArquillianExtension.class) public class TextWebServiceTest { - + @ArquillianResource URL baseURL; // @Inject // private TextWebService textWebService; - - + @Deployment public static WebArchive createDeployment() { System.setProperty("log4j.configurationFile", "log4j2-test.xml"); @@ -66,21 +68,39 @@ public static WebArchive createDeployment() { .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); return archive; } - + // @BeforeEach public void init() { - + } - + + private String streamToString(InputStream inputStream) { + try (ByteArrayOutputStream result = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + for (int length; (length = inputStream.read(buffer)) != -1; ) { + result.write(buffer, 0, length); + } + // StandardCharsets.UTF_8.name() > JDK 7 + return result.toString("UTF-8"); + } catch (IOException e) { + // ignore + } + return ""; + } + @Test @RunAsClient public void test001_TextWebService() throws IOException { // port number must match the arquillian setting HttpUriRequest request = new HttpGet( - new URL(baseURL, "process/1").toExternalForm()); + new URL(baseURL, "process?fileId=123").toExternalForm()); HttpResponse response = HttpClientBuilder.create().build().execute(request); assertEquals(HttpStatus.OK_200, response.getStatusLine().getStatusCode()); + + final String result = streamToString(response.getEntity().getContent()); + System.out.println(result); + assertEquals("Submitted: 123", result); } - + } diff --git a/kx-web/src/test/resources/arquillian.xml b/kx-web/src/test/resources/arquillian.xml new file mode 100644 index 000000000..a22ccb774 --- /dev/null +++ b/kx-web/src/test/resources/arquillian.xml @@ -0,0 +1,49 @@ + + + + + + + 8800 + + + + target/arquillian + + apiDS = new://Resource?type=DataSource + apiDS.JdbcDriver = org.postgresql.Driver + apiDS.JdbcUrl = jdbc:postgresql://localhost:65432/lbac?charSet=UTF-8 + apiDS.UserName = lbac + apiDS.Password = lbac + apiDS.JtaManaged = true + apiDS.LogSql = true + # + # + tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar + tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ + postgresql*.jar,\ + log4j*.jar,slf4j*.jar,hibernate*.jar + + true + + + + + + + diff --git a/tx/src/test/resources/log4j2-test.xml b/kx-web/src/test/resources/log4j2-test.xml similarity index 90% rename from tx/src/test/resources/log4j2-test.xml rename to kx-web/src/test/resources/log4j2-test.xml index b443917b4..643ced19c 100644 --- a/tx/src/test/resources/log4j2-test.xml +++ b/kx-web/src/test/resources/log4j2-test.xml @@ -13,8 +13,8 @@ [%-5p] %d{yyyy-MM-dd HH:mm:ss} %c{1} - %m%n diff --git a/pom.xml b/pom.xml index 9dc9bafc8..65d9103bd 100644 --- a/pom.xml +++ b/pom.xml @@ -34,8 +34,9 @@ agency-api agency snowball - tx-api tx + kx-api + kx-web ui diff --git a/tx-api/pom.xml b/tx-api/pom.xml deleted file mode 100644 index 046da9094..000000000 --- a/tx-api/pom.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - 4.0.0 - - de.ipb-halle - tx-api - 1.0.1 - jar - - TX API - http://github.com/ipb-halle/CRIMSy - - - UTF-8 - - - - - - - - central - https://repo.maven.apache.org/maven2/ - - - sonatype public - https://oss.sonatype.org/content/groups/public/ - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - -Xlint:all - ${project.build.sourceEncoding} - - - - - - maven-assembly-plugin - 2.6 - - - jar-with-dependencies - - - - - - - - - - make-assembly - package - - single - - - - - - - - - - - - - de.ipb-halle - crimsy-api - 1.0.0 - - - - - junit - junit - 4.13.1 - test - - - - - org.slf4j - slf4j-api - 1.7.30 - - - - - javax.persistence - javax.persistence-api - 2.2 - provided - - - - diff --git a/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorEntity.java b/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorEntity.java deleted file mode 100644 index 0a447e277..000000000 --- a/tx-api/src/main/java/de/ipb_halle/tx/file/TermVectorEntity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.tx.file; - -import java.io.Serializable; -import javax.persistence.Column; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Table; - -/** - * - * @author fmauz - */ -@Entity -@Table(name = "termvectors") -public class TermVectorEntity implements Serializable { - - @EmbeddedId - private TermVectorId id; - - @Column(name = "termfrequency") - private int termFrequency; - - public TermVectorEntity() { - } - - public TermVectorId getId() { - return id; - } - - public int getTermFrequency() { - return termFrequency; - } - - public TermVectorEntity setId(TermVectorId id) { - this.id = id; - return this; - } - - public TermVectorEntity setTermFrequency(int termFrequency) { - this.termFrequency = termFrequency; - return this; - } - -} diff --git a/tx/pom.xml b/tx/pom.xml index f91cf782f..9dcdd9178 100644 --- a/tx/pom.xml +++ b/tx/pom.xml @@ -33,8 +33,7 @@ scm:git:https://github.com/ipb-halle/CRIMSy.git https://github.com/ipb-halle/CRIMSy.git - war - + jar @@ -84,7 +83,6 @@ - @@ -138,70 +135,12 @@ - - - - - - - maven-war-plugin - 2.4 - - web - true - ui - - - true - - - ${buildNumber} - - - - - - - - - de.ipb-halle - tx-api - 1.0.1 - - commons-cli diff --git a/tx/src/test/java/de/ipb_halle/tx/test/EntityManagerService.java b/tx/src/test/java/de/ipb_halle/tx/test/EntityManagerService.java deleted file mode 100644 index ca298a866..000000000 --- a/tx/src/test/java/de/ipb_halle/tx/test/EntityManagerService.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package de.ipb_halle.tx.test; - -import javax.ejb.Stateless; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; - -/** - * - * @author fbroda - */ -@Stateless -public class EntityManagerService { - - @PersistenceContext(name = "de.ipb_halle.lbac") - private EntityManager em; - - public EntityManager getEntityManager() { - return em; - } -} diff --git a/ui/pom.xml b/ui/pom.xml index 91c361e1c..73806c139 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -605,23 +605,10 @@ 1.0.0 - - - de.ipb-halle - tx - 1.0.1 - - - org.apache.cxf - cxf-rt-rs-client - - - - de.ipb-halle - tx-api - 1.0.1 + kx-api + 1.0.0 diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java b/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java index 35750f69e..e20e2b59e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/Collection.java @@ -21,6 +21,7 @@ * Collection class represents a collection in the Bioactives Cloud. Model for * collection managment */ +import de.ipb_halle.kx.file.AttachmentHolder; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACObject; import de.ipb_halle.lbac.admission.User; @@ -29,7 +30,6 @@ import de.ipb_halle.lbac.entity.Obfuscatable; import java.io.Serializable; import java.nio.file.Paths; -import de.ipb_halle.tx.file.AttachmentHolder; public class Collection extends ACObject implements Serializable, Obfuscatable, DTO, AttachmentHolder { diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java index d1ca7d32e..b8ad78cb6 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java @@ -17,10 +17,9 @@ */ package de.ipb_halle.lbac.file; +import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.lbac.service.FileService; -import de.ipb_halle.tx.file.FileObject; - import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java index 5f9abedde..fb64e5550 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java @@ -17,9 +17,9 @@ */ package de.ipb_halle.lbac.file; +import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.tx.file.FileObject; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java index 4f0fe708a..fdc403134 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java @@ -17,12 +17,12 @@ */ package de.ipb_halle.lbac.file; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectEntity; +import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.admission.MemberService; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.tx.file.FileObjectEntity; -import de.ipb_halle.tx.file.TermVector; import java.io.Serializable; import java.math.BigInteger; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java index be915c873..df72f4b0b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.file; -import de.ipb_halle.tx.file.AttachmentHolder; +import de.ipb_halle.kx.file.AttachmentHolder; import de.ipb_halle.lbac.search.document.StemmedWordGroup; /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java index 0b6f5b9a0..51cb2d3f5 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java @@ -17,6 +17,10 @@ */ package de.ipb_halle.lbac.file; +import de.ipb_halle.kx.file.AttachmentHolder; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.file.save.FileAnalyser; import de.ipb_halle.lbac.collections.Collection; @@ -30,10 +34,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import de.ipb_halle.tx.file.AttachmentHolder; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; -import de.ipb_halle.tx.file.StemmedWordOrigin; -import de.ipb_halle.tx.file.TermVector; import java.io.InputStream; import java.io.PrintWriter; @@ -58,7 +58,7 @@ public class UploadToCol implements Runnable { protected String fileName; protected FileSaver fileSaver; protected FileAnalyser fileAnalyser; - protected TermVectorEntityService termVectorService; + protected TermVectorService termVectorService; protected Integer fileId; protected FileEntityService fileEntityService; private final Logger logger; @@ -69,7 +69,7 @@ public UploadToCol( User user, AsyncContext asyncContext, CollectionService collectionService, - TermVectorEntityService termVectorService) { + TermVectorService termVectorService) { fileAnalyser = new FileAnalyser(filterDefinition); fileSaver = new FileSaver(fileEntityService, user); this.asyncContext = asyncContext; diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java index 1f1be4aae..bd01292c0 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java @@ -17,11 +17,11 @@ */ package de.ipb_halle.lbac.file.save; +import de.ipb_halle.kx.file.AttachmentHolder; +import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.lbac.util.HexUtil; -import de.ipb_halle.tx.file.AttachmentHolder; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -105,7 +105,7 @@ protected void saveFileToFileSystem(InputStream inputStream, Path fileLocation) } public void updateLanguageOfFile(String language) { - fileObject.setDocument_language(language); + fileObject.setDocumentLanguage(language); fileEntityService.save(fileObject); } diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/DocumentMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/DocumentMessage.java deleted file mode 100644 index 7361c82e1..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/DocumentMessage.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import de.ipb_halle.lbac.search.document.Document; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * document message class for documents - */ -@XmlRootElement -public class DocumentMessage extends Message { - - public static class Builder extends Message.Builder { - private final MessageType HEADER = MessageType.DOCUMENT; - - /** - * set defaults for builder - * @param document - payload - */ - public Builder (Document document){ - super.Header(HEADER); - super.Body(document); - super.Clazz(Document.class); - } - - /** - * build document message - * @return - document message - */ - @Override - public DocumentMessage build() { - return new DocumentMessage(this); - } - @Override - protected Builder self() { - return this; - } - } - private DocumentMessage(Builder builder) { - super(builder); - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/Message.java b/ui/src/main/java/de/ipb_halle/lbac/message/Message.java deleted file mode 100644 index ef39ac5bb..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/Message.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import java.io.Serializable; -import java.util.Objects; - -import javax.xml.bind.annotation.XmlRootElement; - -/*** - * basic class for websocket messages - * a message contains a header, a body (payload), a class describe the payload, message id - * implements a static builder class (build pattern) - */ -@XmlRootElement -public abstract class Message implements Serializable { - - private final static Long serialVersionUID = 1L; - - private int id; - private MessageType header; - private Object body; - private Class clazz; - - public int getId() { - return id; - } - - public String getHeader() { - return header.toString(); - } - - @SuppressWarnings("unchecked") - public Object getBody() { - return castObject(clazz, body); - } - - /** - * static builder, builds the member class - * - * @param - */ - abstract static class Builder> { - - int id = 0; - MessageType header = MessageType.DEFAULT; - Object body = new Object(); - Class clazz = Object.class; - - /** - * basic build methods for id, header, body and clazz - * - * @param id - * @return - */ - public T Id(int id) { - this.id = id; - return self(); - } - - public T Header(String h) { - header = MessageType.byVal(h); - return self(); - } - - public T Header(MessageType m) { - header = Objects.requireNonNull(m); - return self(); - } - - public T Body(Object o) { - body = Objects.requireNonNull(o); - return self(); - } - - public T Clazz(Class zz) { - clazz = zz; - return self(); - } - - /** - * subclasses must override - * - * @return - build method - */ - abstract Message build(); - - /** - * subclasses must override method to return "this" - * - * @return - this - */ - protected abstract T self(); - } - - /** - * generic type for builder - * - * @param builder - */ - @SuppressWarnings("unchecked") - Message(Builder builder) { - id = builder.id; - header = builder.header; - clazz = builder.clazz; - body = castObject(builder.clazz, builder.body); - } - - Message(){ - //nothing - } - - /** - * dynamic cast for payload - * - * @param clazz - describe payload - * @param object - payload - * @param class - * @return - casted payload - */ - @SuppressWarnings("unchecked") - private T castObject(Class clazz, Object object) { - return (T) object; - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/MessageType.java b/ui/src/main/java/de/ipb_halle/lbac/message/MessageType.java deleted file mode 100644 index 0674d1585..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/MessageType.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import java.util.Map; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toMap; - -public enum MessageType { - - DEFAULT, - STATUS, - SIMPLESEARCH, - TERMVECTORSEARCH, - TERMVECTOR, - DOCUMENT, - TAGWORD; - - private static final Map string2Enum = Stream.of(values()).collect(toMap(Object::toString, e -> e)); - - public static MessageType byVal(String val) { - return string2Enum.get(val.toUpperCase()); - } - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/SimpleSearchMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/SimpleSearchMessage.java deleted file mode 100644 index f98f273cf..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/SimpleSearchMessage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * simple search message class - */ -@XmlRootElement -public class SimpleSearchMessage extends Message { - - public static class Builder extends Message.Builder { - - private final MessageType HEADER = MessageType.SIMPLESEARCH; - - /** - * set defaults for builder - * - * @param - payload - */ - public Builder(String status) { - super.Header(HEADER); - super.Body(status); - super.Clazz(String.class); - } - - /** - * build status message - * - * @return - status message - */ - @Override - public SimpleSearchMessage build() { - return new SimpleSearchMessage(this); - } - - @Override - protected Builder self() { - return this; - } - } - - private SimpleSearchMessage(Builder builder) { - super(builder); - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/StatusMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/StatusMessage.java deleted file mode 100644 index 2f674be3e..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/StatusMessage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * status message class - */ -@XmlRootElement -public class StatusMessage extends Message { - - public static class Builder extends Message.Builder { - - private final MessageType HEADER = MessageType.STATUS; - - /** - * set defaults for builder - * - * @param - payload - */ - public Builder(String status) { - super.Header(HEADER); - super.Body(status); - super.Clazz(String.class); - } - - /** - * build status message - * - * @return - status message - */ - @Override - public StatusMessage build() { - return new StatusMessage(this); - } - - @Override - protected Builder self() { - return this; - } - } - - private StatusMessage(Builder builder) { - super(builder); - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/TagWordMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/TagWordMessage.java deleted file mode 100644 index 19af69e12..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/TagWordMessage.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import java.util.Objects; -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement -public class TagWordMessage extends Message { - private int maxResult; - - public static class Builder extends Message.Builder { - - private final MessageType HEADER = MessageType.TAGWORD; - private int maxResult = 50; - private String tagWord; - - /** - * set defaults for builder - * - * @param - payload - */ - public Builder() { - super.Header(HEADER); - super.Clazz(String.class); - } - - - public Builder setMaxResult (int maxResult){ - if (maxResult > 0) this.maxResult = maxResult; - return this; - } - - public Builder setTagWord(String tagWord){ - this.tagWord = Objects.requireNonNull(tagWord); - super.body = this.tagWord; - return this; - } - - /** - * build status message - * - * @return - status message - */ - @Override - public TagWordMessage build() { - return new TagWordMessage(this); - } - - @Override - protected Builder self() { - return this; - } - } - - //*** getter *** - - public int getMaxResult() { - return maxResult; - } - - private TagWordMessage(Builder builder) { - super(builder); - this.maxResult = builder.maxResult; - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorMessage.java deleted file mode 100644 index 883cf30d8..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorMessage.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import de.ipb_halle.lbac.search.document.DocumentSearchRequest; - -import java.io.Serializable; -import java.util.*; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * message class for rest-api calls - */ -@XmlRootElement -@XmlAccessorType(XmlAccessType.FIELD) -public class TermVectorMessage implements Serializable { - - private final static long serialVersionUID = 1L; - - private MessageType messageType = MessageType.TERMVECTOR; - private List docIds = new ArrayList<>(); - private Map termVectorResult = new HashMap<>(); - private Integer maxResult = 50; - private DocumentSearchRequest searchRequest; - - //*** getter and setter *** - public DocumentSearchRequest getSearchRequest() { - return searchRequest; - } - - public void setSearchRequest(DocumentSearchRequest searchRequest) { - this.searchRequest = searchRequest; - } - - public MessageType getMessageType() { - return messageType; - } - - public List getDocIds() { - return docIds; - } - - public void setDocIds(List docIds) { - this.docIds = docIds; - } - - public Integer getMaxResult() { - return maxResult; - } - - public void setMaxResult(Integer maxResult) { - this.maxResult = maxResult; - } - - public Map getTermVectorResult() { - return termVectorResult; - } - - public void setTermVectorResult(Map termVectorResult) { - this.termVectorResult = termVectorResult; - } - - //*** implementation *** - public TermVectorMessage() { - } - - /** - * constructor - * - * @param docIds - * @param maxResult - * @param searchRequest - */ - public TermVectorMessage(List docIds, Integer maxResult, DocumentSearchRequest searchRequest) { - this.docIds = Objects.requireNonNull(docIds); - this.maxResult = maxResult; - this.searchRequest = searchRequest; - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorResultMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorResultMessage.java deleted file mode 100644 index e10b99ae3..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorResultMessage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import java.util.Map; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * message class for termvectors - */ -@XmlRootElement -public class TermVectorResultMessage extends Message { - - public static class Builder extends Message.Builder { - - private final MessageType HEADER = MessageType.TERMVECTOR; - - /** - * set defaults for builder - * - * @param - termvector - payload - */ - public Builder(Map termvector) { - super.Header(HEADER); - super.Body(termvector); - } - - /** - * build document message - * - * @return - termvector message - */ - @Override - public TermVectorResultMessage build() { - return new TermVectorResultMessage(this); - } - - @Override - protected Builder self() { - return this; - } - } - - private TermVectorResultMessage(Builder builder) { - super(builder); - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorSearchMessage.java b/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorSearchMessage.java deleted file mode 100644 index 4bd359273..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/message/TermVectorSearchMessage.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.message; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * termvector search message - */ -@XmlRootElement -public class TermVectorSearchMessage extends Message { - private int maxResult; - - public static class Builder extends Message.Builder { - - private final MessageType HEADER = MessageType.TERMVECTORSEARCH; - private int maxResult = 50; - - /** - * set defaults for builder - * - * @param - payload - */ - public Builder() { - super.Header(HEADER); - super.Body(new ArrayList<>()); - super.Clazz(List.class); - } - - - public Builder setMaxResult (int maxResult){ - if (maxResult > 0) this.maxResult = maxResult; - return this; - } - - public Builder setDocIds(List docIds){ - super.Body(Objects.requireNonNull(docIds)); - return this; - } - - /** - * build message - * - * @return - termvector search message - */ - @Override - public TermVectorSearchMessage build() { - return new TermVectorSearchMessage(this); - } - - @Override - protected Builder self() { - return this; - } - } - - //*** getter *** - - public int getMaxResult() { - return maxResult; - } - - public TermVectorSearchMessage(){ - } - - @SuppressWarnings("unchecked") - public List getDocIds(){ - return (List) super.getBody(); - } - - private TermVectorSearchMessage(Builder builder) { - super(builder); - this.maxResult = builder.maxResult; - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java index 90dc34211..5c7772920 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/Document.java @@ -20,11 +20,10 @@ /** * This class stores information about a document. */ -import de.ipb_halle.tx.file.TermFrequencyList; -import de.ipb_halle.tx.file.TermFrequency; +import de.ipb_halle.kx.termvector.TermFrequencyList; +import de.ipb_halle.kx.termvector.TermFrequency; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.message.LocalUUIDConverter; import de.ipb_halle.lbac.search.SearchTarget; import de.ipb_halle.lbac.search.Searchable; import de.ipb_halle.lbac.search.bean.Type; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java index 66f2c094c..b28e52b7f 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentEntityGraphBuilder.java @@ -17,9 +17,9 @@ */ package de.ipb_halle.lbac.search.document; +import de.ipb_halle.kx.file.FileObjectEntity; +import de.ipb_halle.kx.termvector.TermVectorEntity; import de.ipb_halle.lbac.collections.CollectionEntity; -import de.ipb_halle.tx.file.FileObjectEntity; -import de.ipb_halle.tx.file.TermVectorEntity; import de.ipb_halle.lbac.search.EntityGraphBuilder; import de.ipb_halle.crimsy_api.AttributeType; import de.ipb_halle.lbac.search.lang.EntityGraph; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchRequest.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchRequest.java index a9028ffed..34e1cbf2d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchRequest.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchRequest.java @@ -23,7 +23,6 @@ * instance of SearchRequest in the ui and to transmit it in xml serialized form * to the backend. */ -import de.ipb_halle.lbac.message.LocalUUIDConverter; import de.ipb_halle.lbac.webservice.WebRequest; import java.io.Serializable; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java index 77ae683eb..5bd1c78d7 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java @@ -17,15 +17,16 @@ */ package de.ipb_halle.lbac.search.document; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectEntity; +import de.ipb_halle.kx.termvector.TermFrequency; + import de.ipb_halle.lbac.webclient.XmlSetWrapper; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.tx.file.TermFrequency; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.tx.file.FileObjectEntity; import de.ipb_halle.lbac.file.FileSearchRequest; import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchQueryStemmer; @@ -154,7 +155,7 @@ public DocumentSearchState actionStartDocumentSearch( docIds.add(d.getId()); } - TermOcurrence totalTerms = termVectorEntityService.getTermVectorForSearch( + TermOccurrence totalTerms = getTermOccurrence( docIds, normalizedTerms.getAllStemmedWords()); @@ -168,6 +169,29 @@ public DocumentSearchState actionStartDocumentSearch( return searchState; } + /** + * getTermOccurrence with aggregation, grouping and order result + * + * @param fileIds - document ids + * @param searchTerms + * @return - list (String word, Integer wordCount) + * + */ + @SuppressWarnings("unchecked") + public TermOccurrence getTermOccurrence( + List fileIds, + Set searchTerms) { + TermOccurrence occurence = new TermOccurrence(); + for (Integer fileId : fileIds) { + List frequencies = termVectorService.getTermFrequencies(fileId, searchTerms); + for (TermFrequency freq : frequencies) { + occurrence.addOccurrence(fileId, freq.getTerm(), freq.getFrequency()); + } + } + return occurrence; + } + + private int loadTotalCountOfFiles() { Query q = em.createNativeQuery(SQL_LOAD_DOCUMENT_COUNT); @SuppressWarnings("unchecked") @@ -201,7 +225,7 @@ public SearchResult loadDocuments(SearchRequest request) { result.addResults(foundDocs); List docIds = getDocIds(foundDocs); - TermOcurrence totalTerms = termVectorEntityService.getTermVectorForSearch( + TermOccurrence totalTerms = getTermOccurence( docIds, getWordRoots(request)); calculateWordCountOfDocs(foundDocs, totalTerms); @@ -227,7 +251,7 @@ private Set getWordRoots(SearchRequest request) { return new HashSet<>(); } - private void calculateWordCountOfDocs(List foundDocs, TermOcurrence totalTerms) { + private void calculateWordCountOfDocs(List foundDocs, TermOccurrence totalTerms) { for (Searchable searchable : foundDocs) { Document d = (Document) searchable; d.setWordCount(getLengthOfDocument(d.getId())); @@ -323,7 +347,7 @@ private Document convertFileObjectToDocument(FileObject fo) { d.setCollectionId(fo.getCollectionId()); d.setNodeId(nodeService.getLocalNodeId()); d.setNode(nodeService.getLocalNode()); - d.setLanguage(fo.getDocument_language()); + d.setLanguage(fo.getDocumentLanguage()); d.setCollection(collectionService.loadById(fo.getCollectionId())); d.setPath(fo.getFileLocation()); d.setContentType(fo.getName().split("\\.")[fo.getName().split("\\.").length - 1]); diff --git a/ui/src/main/java/de/ipb_halle/lbac/message/LocalUUIDConverter.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/LocalUUIDConverter.java similarity index 96% rename from ui/src/main/java/de/ipb_halle/lbac/message/LocalUUIDConverter.java rename to ui/src/main/java/de/ipb_halle/lbac/search/document/LocalUUIDConverter.java index 097fabc25..35e7f3b3a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/message/LocalUUIDConverter.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/LocalUUIDConverter.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.message; +package de.ipb_halle.lbac.search.document; import java.util.UUID; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOcurrence.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/search/document/TermOcurrence.java rename to ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java index 5aa923ce6..121000892 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOcurrence.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java @@ -13,7 +13,7 @@ * * @author fmauz */ -public class TermOcurrence { +public class TermOccurrence { private Map> termOccurences = new HashMap<>(); diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java index 1f534e678..0f6564423 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java @@ -17,17 +17,9 @@ */ package de.ipb_halle.lbac.search.document.download; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import javax.enterprise.context.RequestScoped; -import javax.inject.Inject; -import javax.inject.Named; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.lbac.admission.ACListService; +import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.User; @@ -42,7 +34,17 @@ import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.jsf.SendFileBean; -import de.ipb_halle.tx.file.FileObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java index 1be634b24..ccaee3060 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java @@ -17,6 +17,18 @@ */ package de.ipb_halle.lbac.search.document.download; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.lbac.admission.ACListService; +import de.ipb_halle.lbac.admission.ACPermission; +import de.ipb_halle.lbac.admission.MemberService; +import de.ipb_halle.lbac.admission.User; +import de.ipb_halle.lbac.collections.Collection; +import de.ipb_halle.lbac.collections.CollectionService; +import de.ipb_halle.lbac.entity.Node; +import de.ipb_halle.lbac.file.FileEntityService; +import de.ipb_halle.lbac.webservice.service.LbacWebService; +import de.ipb_halle.lbac.webservice.service.NotAuthentificatedException; + import java.io.File; import java.io.InputStream; @@ -32,18 +44,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import de.ipb_halle.lbac.admission.ACListService; -import de.ipb_halle.lbac.admission.ACPermission; -import de.ipb_halle.lbac.admission.MemberService; -import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.lbac.webservice.service.LbacWebService; -import de.ipb_halle.lbac.webservice.service.NotAuthentificatedException; - /** * Webservice for download of documents. * @@ -108,4 +108,4 @@ private Response responseWithLocalFile(FileObject fileObject) { } return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM).build(); } -} \ No newline at end of file +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java b/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java deleted file mode 100644 index c35582ec2..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityService.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.search.termvector; - -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.tx.file.TermVector; -import de.ipb_halle.tx.file.TermVectorEntity; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.tx.file.StemmedWordOrigin; -import de.ipb_halle.lbac.message.TermVectorMessage; -import de.ipb_halle.lbac.search.document.TermOcurrence; - -import java.io.Serializable; -import java.util.ArrayList; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.PostConstruct; -import javax.ejb.Stateless; -import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import org.apache.commons.lang.exception.ExceptionUtils; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -@Stateless -public class TermVectorEntityService implements Serializable { - - @PersistenceContext(name = "de.ipb_halle.lbac") - public EntityManager em; - - private Logger logger; - - - @Inject - private FileEntityService fileEntityService; - - protected final String SQL_TERMVECTORS_BY_ID - = "select wordroot,file_id,termfrequency" - + " from termvectors" - + " where file_id in (:id)" - + "order by termfrequency desc"; - - protected final String SQL_TERMVECTORS_BY_ID_AND_WORDS - = "select wordroot,file_id,termfrequency" - + " from termvectors" - + " where file_id in (:id)" - + " and wordroot in(:words)" - + "order by termfrequency desc"; - - protected final String SQL_TOTAL_WORD_COUNT - = "select sum(t.termfrequency) from termvectors t"; - - protected final String SQL_DELETE_UNSTEMMED_WORDS_BY_ID - = "delete from unstemmed_words tv where tv.file_id in(:fileId)"; - - protected final String SQL_DELETE_TERMVECTOR_BY_ID - = "delete from termvectors tv where tv.file_id in(:fileId)"; - - protected final String SQL_INSERT_UNSTEMMED_WORD - = "insert into unstemmed_words (unstemmed_word, file_id, stemmed_word) values" - + "(:unstemmed_word,:did,:stemmed_word)"; - - protected final String SQL_LOAD_UNSTEMMED_WORD - = "select unstemmed_word" - + " from unstemmed_words" - + " where file_id in (:id)" - + "AND stemmed_word=:wr"; - - protected final String SQL_LOAD_UNSTEMMED_WORDS - = "select stemmed_word,unstemmed_word" - + " from unstemmed_words" - + " where file_id in (:id)" - + "AND stemmed_word in (:wr)"; - - protected final String SQL_DELETE_ALL_UNSTEMMED_WORDS - = "delete from unstemmed_words"; - - protected final String SQL_DELETE_ALL_TERMVECTORS - = "delete from termvectors"; - - /** - * init. check injection points - */ - @PostConstruct - public void FileEntityServiceInit() { - logger = LogManager.getLogger(this.getClass()); - if (em == null) { - logger.error("Injection failed for Entitimanager em."); - } - } - - /** - * getTermvector with aggregation, grouping and order result - * - * - * @param docIds - document ids - * @param maxResult - return top max. rows for result set - * @return - list (String word, Integer wordCount) - */ - @SuppressWarnings("unchecked") - public Map getTermVector(List docIds, Integer maxResult) { - try { - if (docIds.isEmpty()) { - return new HashMap<>(); - } - List tvList = loadTermvectorsForDocuments(docIds, maxResult); - - Map results = mergeTermvectors(tvList); - - ArrayList list = sortValues(results); - - return getMostFrequentTerms(list, maxResult, results); - - } catch (Exception e) { - logger.error(ExceptionUtils.getStackTrace(e)); - return new HashMap<>(); - } - } - - /** - * getTermvector with aggregation, grouping and order result - * - * - * @param docIds - document ids - * @param searchTerms - * @return - list (String word, Integer wordCount) - * - */ - @SuppressWarnings("unchecked") - public TermOcurrence getTermVectorForSearch( - List docIds, - Set searchTerms) { - TermOcurrence back = new TermOcurrence(); - if (searchTerms.isEmpty()) { - return back; - } - try { - if (docIds.isEmpty()) { - return back; - } - List list = new ArrayList<>(); - - List entities = this.em.createNativeQuery( - SQL_TERMVECTORS_BY_ID_AND_WORDS, TermVectorEntity.class) - .setParameter("id", docIds) - .setParameter("words", searchTerms) - .getResultList(); - for (TermVectorEntity entity : entities) { - list.add(new TermVector(entity)); - } - - for (TermVector tv : list) { - back.addOccurence(tv.getFileId(), tv.getWordRoot(), tv.getTermFrequency()); - } - - } catch (Exception e) { - logger.error(e.getMessage()); - for (StackTraceElement el : e.getStackTrace()) { - logger.error(el); - } - return new TermOcurrence(); - } - return back; - } - - /** - * wrapper for getTermVector, set maxResult = 50 (see above) - * - * @param termVectorMessage - contains parameters - * @return - map (String word, Integer wordCount), maxResult = 50 - */ - public Map getTermVector(TermVectorMessage termVectorMessage) { - return getTermVector(termVectorMessage.getDocIds(), termVectorMessage.getMaxResult()); - } - - /** - * Sums all words from all documents of the local node - * - * @return - */ - public int getSumOfAllWordsFromAllDocs() { - java.math.BigInteger sum = (java.math.BigInteger) this.em.createNativeQuery(SQL_TOTAL_WORD_COUNT).getSingleResult(); - if (sum == null) { - return 0; - } else { - return sum.intValue(); - } - } - - /** - * Deletes the termvectors and unstemmed words for all documents in a - * collection - * - * @param c - */ - public void deleteTermVectorOfCollection(Collection c) { - - List files = fileEntityService.getAllFilesInCollection(c); - - for (FileObject f : files) { - - this.em.createNativeQuery(SQL_DELETE_UNSTEMMED_WORDS_BY_ID) - .setParameter("fileId", f.getId()) - .executeUpdate(); - this.em.flush(); - } - - for (FileObject f : files) { - this.em.createNativeQuery(SQL_DELETE_TERMVECTOR_BY_ID) - .setParameter("fileId", f.getId()) - .executeUpdate(); - this.em.flush(); - } - } - - /** - * Saves the list of unstemmed words for a stemmed word of a document - * - * @param wordOrigins - * @param documentId - */ - public void saveUnstemmedWordsOfDocument( - List wordOrigins, - Integer documentId) { - for (StemmedWordOrigin swo : wordOrigins) { - for (String s : swo.getOriginalWord()) { - this.em.createNativeQuery(SQL_INSERT_UNSTEMMED_WORD) - .setParameter("unstemmed_word", s) - .setParameter("did", documentId) - .setParameter("stemmed_word", swo.getStemmedWord()) - .executeUpdate(); - this.em.flush(); - } - } - } - - /** - * Loads the list of unstemmed words for a document and a stemmed word. - * - * @param documentId - * @param wordRoot - * @return - */ - public List loadUnstemmedWordsOfDocument( - Integer documentId, - String wordRoot) { - List stemmedWords = new ArrayList<>(); - - @SuppressWarnings("unchecked") - List words = this.em.createNativeQuery( - SQL_LOAD_UNSTEMMED_WORD) - .setParameter("id", documentId) - .setParameter("wr", wordRoot) - .getResultList(); - StemmedWordOrigin swo = new StemmedWordOrigin(); - stemmedWords.add(swo); - for (String o : words) { - swo.getOriginalWord().add(o); - swo.setStemmedWord(wordRoot); - - } - return stemmedWords; - } - - /** - * Loads the list of unstemmed words for a document and a stemmed word. - * - * @param documentId - * @param wordRoot - * @return - */ - public List loadUnstemmedWordsOfDocument( - Integer documentId, - List wordRoot) { - List stemmedWords = new ArrayList<>(); - @SuppressWarnings("unchecked") - List words = this.em.createNativeQuery( - SQL_LOAD_UNSTEMMED_WORDS) - .setParameter("id", documentId) - .setParameter("wr", wordRoot) - .getResultList(); - - for (Object[] o : words) { - StemmedWordOrigin swo = new StemmedWordOrigin(); - stemmedWords.add(swo); - swo.getOriginalWord().add((String) o[1]); - swo.setStemmedWord((String) o[0]); - - } - return stemmedWords; - } - - /** - * Deletes all termvectors and all unstemmed words from database. - */ - public void deleteTermVectors() { - this.em.createNativeQuery(SQL_DELETE_ALL_UNSTEMMED_WORDS).executeUpdate(); - this.em.createNativeQuery(SQL_DELETE_ALL_TERMVECTORS).executeUpdate(); - this.em.flush(); - } - - /** - * Loads the termvectors for the given list of documents. The length of the - * results is limited by maxresults - * - * @param docIds - * @param maxResult - * @return - */ - private List loadTermvectorsForDocuments( - List docIds, - Integer maxResult) { - List tvList = new ArrayList<>(); - for (Integer id : docIds) { - // Loads the termvector for a documentid and limits - // the length by the given maximum result length - @SuppressWarnings("unchecked") - List entities = this.em.createNativeQuery( - SQL_TERMVECTORS_BY_ID, - TermVectorEntity.class) - .setParameter("id", id) - .getResultList(); - - List list = new ArrayList<>(); - for (TermVectorEntity entity : entities) { - list.add(new TermVector(entity)); - } - tvList.addAll(list.subList(0, Math.min(maxResult, list.size()))); - } - return tvList; - } - - /** - * Merges the termvectors from different documents to one hashmap - * - * @param tvList - * @return - */ - private Map mergeTermvectors(List tvList) { - Map results = new HashMap<>(); - for (TermVector tve : tvList) { - if (results.containsKey(tve.getWordRoot())) { - results.put(tve.getWordRoot(), results.get(tve.getWordRoot()) + tve.getTermFrequency()); - } else { - results.put(tve.getWordRoot(), tve.getTermFrequency()); - } - } - return results; - } - - /** - * Returns the sorted (desc) list of the values of the hashmap - * - * @param results - * @return - */ - private ArrayList sortValues(Map results) { - java.util.Collection col = results.values(); - @SuppressWarnings("unchecked") - ArrayList list = new ArrayList<>(col); - Collections.sort(list); - Collections.reverse(list); - return list; - } - - /** - * get the most frequent termvectors limit by maxResults. If the list is - * shorter than max results the complete list is given back - * - * @param list - * @param maxResult - * @param results - * @return - */ - private Map getMostFrequentTerms( - ArrayList list, - Integer maxResult, - Map results) { - if (list.size() < maxResult) { - return results; - } - int treshhold = list.get(maxResult - 1); - Map filteredResults = new HashMap<>(); - int addedWords = 0; - for (String s : results.keySet()) { - if (results.get(s) >= treshhold) { - addedWords++; - filteredResults.put(s, results.get(s)); - } - if (addedWords >= maxResult) { - return filteredResults; - } - } - return filteredResults; - } - -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java index 42c7c57a2..1b839750e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java @@ -17,6 +17,9 @@ */ package de.ipb_halle.lbac.search.wordcloud; +import de.ipb_halle.kx.termvector.TermFrequency; +import de.ipb_halle.kx.termvector.TermFrequencyList; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.UserBean; import de.ipb_halle.lbac.admission.LoginEvent; import de.ipb_halle.lbac.collections.CollectionBean; @@ -25,10 +28,7 @@ import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.search.document.DocumentSearchState; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.CloudNodeService; -import de.ipb_halle.tx.file.TermFrequency; -import de.ipb_halle.tx.file.TermFrequencyList; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -85,7 +85,7 @@ public class WordCloudBean implements Serializable { private CollectionBean collectionBean; @Inject - private TermVectorEntityService termVectorService; + private TermVectorService termVectorService; @Inject private WordCloudWebClient tempClient; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java index 67856882c..e4d793ae6 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebService.java @@ -17,15 +17,15 @@ */ package de.ipb_halle.lbac.search.wordcloud; +import de.ipb_halle.kx.termvector.TermFrequencyList; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.search.document.DocumentSearchState; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.webservice.service.LbacWebService; import de.ipb_halle.lbac.webservice.service.NotAuthentificatedException; -import de.ipb_halle.tx.file.TermFrequencyList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -61,7 +61,7 @@ public class WordCloudWebService extends LbacWebService { private CollectionService collectionService; @Inject - private TermVectorEntityService termVectorService; + private TermVectorService termVectorService; private Logger logger; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java index a2f519613..90506b55a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMerger.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.search.wordcloud; +import de.ipb_halle.kx.termvector.TermFrequency; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.tx.file.TermFrequency; import java.io.Serializable; import java.util.List; diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java index 8bdf28203..628a16a74 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java @@ -99,7 +99,7 @@ public void testSave() { FileObject fE = new FileObject(); fE.setCollectionId(col.getId()); fE.setCreated(new Date()); - fE.setDocument_language("en"); + fE.setDocumentLanguage("en"); fE.setFileLocation("testFile.pdf"); fE.setHash("testHash"); fE.setName("testFile"); diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java index 9e871edb8..5ad91a1cc 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java @@ -101,7 +101,7 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS cmap.put("id", id); FileObject fo = fileEntityService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); - Assert.assertEquals("en", fo.getDocument_language()); + Assert.assertEquals("en", fo.getDocumentLanguage()); Assert.assertEquals("Document1.pdf", fo.getName()); Assert.assertEquals(publicUser.getId(), fo.getUserId()); @@ -112,7 +112,7 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS cmap.put("id", id); fo = fileEntityService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); - Assert.assertEquals("en", fo.getDocument_language()); + Assert.assertEquals("en", fo.getDocumentLanguage()); Assert.assertEquals("DocumentX.docx", fo.getName()); Assert.assertEquals(publicUser.getId(), fo.getUserId()); @@ -123,7 +123,7 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS cmap.put("id", id); fo = fileEntityService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); - Assert.assertEquals("en", fo.getDocument_language()); + Assert.assertEquals("en", fo.getDocumentLanguage()); Assert.assertEquals("TestTabelle.xlsx", fo.getName()); Assert.assertEquals(publicUser.getId(), fo.getUserId()); @@ -135,7 +135,7 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS cmap.put("id", id); fo = fileEntityService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); - Assert.assertEquals("de", fo.getDocument_language()); + Assert.assertEquals("de", fo.getDocumentLanguage()); Assert.assertEquals("a9eed28584c7e6df1d061c77884820524a7d2b4c6644ef5d13b0c2daedaf4d10d040b7c7380df448f91a28eb7fba94cf0b4a964ae141032c63a0b571aeaa5ccf", fo.getHash()); Assert.assertEquals("TestTabelle.xlsx", fo.getName()); Assert.assertEquals(publicUser.getId(), fo.getUserId()); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java index bfd9c5270..430149f69 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java @@ -233,7 +233,7 @@ private FileObject createFileObject(String location) { FileObject fO = new FileObject(); fO.setCollectionId(readableCollection.getId()); fO.setCreated(new Date()); - fO.setDocument_language("en"); + fO.setDocumentLanguage("en"); fO.setFileLocation(location); fO.setName(location); fO.setUserId(publicUser.getId()); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java index 88754b8eb..73871c5c3 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java @@ -182,7 +182,7 @@ private FileObject createFileObject(String location, Collection collection) { FileObject fO = new FileObject(); fO.setCollectionId(collection.getId()); fO.setCreated(new Date()); - fO.setDocument_language("en"); + fO.setDocumentLanguage("en"); fO.setFileLocation(location); fO.setName(location); fO.setUserId(adminUser.getId()); @@ -222,4 +222,4 @@ public static WebArchive createDeployment() { .addClass(DocumentWebService.class); return UserBeanDeployment.add(deployment); } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java deleted file mode 100644 index 5f07f1fcb..000000000 --- a/ui/src/test/java/de/ipb_halle/lbac/search/termvector/TermVectorEntityServiceTest.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.search.termvector; - -import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.admission.ACList; -import de.ipb_halle.lbac.admission.ACListService; -import de.ipb_halle.lbac.admission.ACPermission; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.service.FileService; -import de.ipb_halle.testcontainers.PostgresqlContainerExtension; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.tx.file.TermVector; -import de.ipb_halle.tx.file.StemmedWordOrigin; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit5.ArquillianExtension; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.jupiter.api.AfterEach; -import org.junit.Assert; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.MethodOrderer.MethodName; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * - * @author fmauz - */ -@TestMethodOrder(MethodName.class) -@ExtendWith(PostgresqlContainerExtension.class) -@ExtendWith(ArquillianExtension.class) -public class TermVectorEntityServiceTest extends TestBase { - - @PersistenceContext(name = "de.ipb_halle.lbac") - protected EntityManager em; - - @Inject - private TermVectorEntityService termVectorEntityService; - - @Inject - private CollectionService collectionService; - - @Inject - private FileEntityService fileEntityService; - - @Inject - private ACListService aclistService; - - private Random random = new Random(); - private User user; - - @Deployment - public static WebArchive createDeployment() { - return prepareDeployment("TermVectorEntityServiceTest.war") - .addClass(FileEntityService.class) - .addClass(FileService.class) - .addClass(FileObject.class) - .addClass(TermVectorEntityService.class) - .addClass(CollectionService.class); - } - - @AfterEach - public void cleanUp() { - - //entityManagerService.doSqlUpdate(String.format("DELETE FROM usersgroups WHERE name='%s'", user.getName())); - - } - - @Test - public void test001_termVectorEntityService() { - - user = createUser( - "testuser", - "testuser"); - - termVectorEntityService.deleteTermVectors(); - ACList acl = new ACList(); - acl.setName("test"); - acl.addACE(user, ACPermission.values()); - aclistService.save(acl); - Collection col1 = createCollection("collection1", acl, user); - Collection col2 = createCollection("collection2", acl, user); - col1 = collectionService.save(col1); - col2 = collectionService.save(col2); - - FileObject fE1 = createFileObject(col1, "en", "file1", user); - FileObject fE2 = createFileObject(col1, "en", "file2", user); - FileObject fE3 = createFileObject(col1, "de", "file3", user); - FileObject fE4 = createFileObject(col2, "en", "file4", user); - - fE1 = fileEntityService.save(fE1); - fE2 = fileEntityService.save(fE2); - fE3 = fileEntityService.save(fE3); - fE4 = fileEntityService.save(fE4); - - List vectors = new ArrayList<>(); - - vectors.add(new TermVector("testStemWord", fE1.getId(), 3)); - vectors.add(new TermVector("testStemWord2", fE1.getId(), 4)); - vectors.add(new TermVector("testStemWord", fE2.getId(), 5)); - vectors.add(new TermVector("testStemWord3", fE3.getId(), 7)); - vectors.add(new TermVector("testStemWord", fE4.getId(), 11)); - - fileEntityService.saveTermVectors(vectors); - - List ids = new ArrayList<>(); - Map results = termVectorEntityService.getTermVector(ids, 10); - Assert.assertTrue(results.isEmpty()); - - ids.add(fE1.getId()); - results = termVectorEntityService.getTermVector(ids, 10); - Assert.assertEquals(2, results.keySet().size()); - int sum = 0; - sum = results.values().stream().map((i) -> i).reduce(sum, Integer::sum); - Assert.assertEquals(7, sum); - - ids.add(fE2.getId()); - ids.add(fE3.getId()); - ids.add(fE4.getId()); - results = termVectorEntityService.getTermVector(ids, 10); - Assert.assertEquals(3, results.keySet().size()); - sum = 0; - sum = results.values().stream().map((i) -> i).reduce(sum, Integer::sum); - Assert.assertEquals(30, sum); - - results = termVectorEntityService.getTermVector(ids, 2); - Assert.assertEquals(2, results.keySet().size()); - int mostFrequentWord = results.get("testStemWord"); - Assert.assertEquals(19, mostFrequentWord); - int secondMostFrequentWord = results.get("testStemWord3"); - Assert.assertEquals(7, secondMostFrequentWord); - - int totalSum = termVectorEntityService.getSumOfAllWordsFromAllDocs(); - Assert.assertTrue(totalSum >= 30); - - termVectorEntityService.deleteTermVectorOfCollection(col2); - totalSum = termVectorEntityService.getSumOfAllWordsFromAllDocs(); - Assert.assertEquals(19, totalSum); - - StemmedWordOrigin wordOrigin = new StemmedWordOrigin(); - Set set = new HashSet<>(); - set.addAll(Arrays.asList("origalWord1", "origalWord2", "origalWord3")); - wordOrigin.setOriginalWord(set); - wordOrigin.setStemmedWord("testStemWord"); - - termVectorEntityService.saveUnstemmedWordsOfDocument(Arrays.asList(wordOrigin), fE1.getId()); - - List< StemmedWordOrigin> words = termVectorEntityService.loadUnstemmedWordsOfDocument(fE1.getId(), "testStemWord"); - Assert.assertEquals("Loading of unstemmed words not correct", 1, words.size()); - Assert.assertEquals(3, words.get(0).getOriginalWord().size()); - - } - - private Collection createCollection(String name, ACList acl, User owner) { - Collection col = new Collection(); - col.setNode(this.nodeService.getLocalNode()); - col.setName(name); - col.setDescription(name); - col.setIndexPath("/doc/test"); - col.setACList(acl); - col.setOwner(owner); - return col; - } - - private FileObject createFileObject(Collection col, String language, String fileName, User owner) { - FileObject fO = new FileObject(); - fO.setCollectionId(col.getId()); - fO.setCreated(new Date()); - fO.setDocument_language(language); - fO.setFileLocation(fileName); - fO.setHash(fileName); - fO.setName(fileName); - fO.setUserId(owner.getId()); - return fO; - } -} From 05d77c94e5e8b6b3905536f20fe185ca3d6de7bf Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Thu, 21 Sep 2023 13:58:45 +0200 Subject: [PATCH 08/28] Emergency commit after mess-up --- .../ipb_halle/kx/file/FileObjectService.java | 141 +++++++++- .../kx/termvector/TermVectorService.java | 27 +- .../de/ipb_halle/kx/service/FileAnalyser.java | 9 +- .../kx/service}/FileAnalyserTest.java | 6 +- .../lbac/collections/CollectionBean.java | 13 +- .../lbac/collections/CollectionOperation.java | 25 +- .../collections/CollectionOrchestrator.java | 6 +- .../ipb_halle/lbac/file/FileDeleteExec.java | 9 +- .../lbac/file/FileDeleteWebService.java | 7 +- .../lbac/file/FileEntityService.java | 242 ------------------ .../lbac/file/FileSearchRequest.java | 8 +- .../lbac/file/FileUploadWebService.java | 14 +- .../de/ipb_halle/lbac/file/UploadToCol.java | 37 +-- .../ipb_halle/lbac/file/save/FileSaver.java | 14 +- .../lbac/search/SearchQueryStemmer.java | 16 +- .../ipb_halle/lbac/search/SearchService.java | 11 +- .../lbac/search/bean/SearchBean.java | 24 +- .../lbac/search/bean/SearchFilter.java | 3 +- .../document/DocumentSearchService.java | 45 +--- .../search/document/DocumentSearchState.java | 6 +- .../lbac/search/document/TermOccurrence.java | 2 +- .../download/DocumentDownloadBean.java | 17 +- .../document/download/DocumentWebService.java | 6 +- .../search/relevance/RelevanceCalculator.java | 25 +- .../lbac/admission/ACListServiceTest.java | 4 - .../lbac/admission/MemberServiceTest.java | 6 - .../DeactivateGroupOrchestratorTest.java | 6 +- .../group/DeactivateGroupWebClientTest.java | 6 +- .../group/DeactivateGroupWebServiceTest.java | 4 +- .../membership/MembershipWebClientTest.java | 7 +- .../membership/MembershipWebServiceTest.java | 7 +- .../ipb_halle/lbac/base/DocumentCreator.java | 21 +- .../java/de/ipb_halle/lbac/base/TestBase.java | 20 +- .../lbac/collections/CollectionBeanTest.java | 12 +- .../collections/CollectionOperationTest.java | 19 +- .../CollectionOrchestratorTest.java | 6 +- .../CollectionPermissionAnalyserTest.java | 6 +- .../collections/CollectionServiceTest.java | 6 +- .../collections/CollectionWebClientTest.java | 4 +- .../collections/CollectionWebServiceTest.java | 6 +- .../collections/mock/CollectionBeanMock.java | 12 +- ...ceMock.java => FileObjectServiceMock.java} | 25 +- .../lbac/file/FileEntityServiceTest.java | 123 --------- .../ipb_halle/lbac/file/UploadToColTest.java | 32 +-- ...ceMock.java => FileObjectServiceMock.java} | 14 +- .../lbac/file/mock/UploadToColMock.java | 12 +- .../lbac/file/save/FileSaverTest.java | 24 +- .../lbac/globals/KeyManagerTest.java | 4 - .../bean/save/MaterialEditSaverTest.java | 25 +- .../lbac/search/SearchOrchestratorTest.java | 6 +- .../lbac/search/SearchQueryStemmerTest.java | 18 +- .../lbac/search/SearchServiceTest.java | 13 +- .../lbac/search/SearchWebClientTest.java | 6 +- .../lbac/search/SearchWebServiceTest.java | 6 +- .../search/bean/NetObjectPresenterTest.java | 6 +- .../lbac/search/bean/SearchBeanTest.java | 10 +- .../document/DocumentSearchServiceTest.java | 14 +- .../download/DocumentDownloadBeanTest.java | 52 ++-- .../download/DocumentWebServiceTest.java | 52 ++-- .../relevance/RelevanceCalculatorTest.java | 10 +- .../search/wordcloud/WordCloudBeanTest.java | 12 +- .../wordcloud/WordCloudWebServiceTest.java | 12 +- .../wordcloud/WordTermListMergerTest.java | 2 +- .../mock/WordCloudWebServiceMock.java | 4 +- .../lbac/service/InfoObjectServiceTest.java | 4 - 65 files changed, 485 insertions(+), 866 deletions(-) rename {ui/src/test/java/de/ipb_halle/lbac/file/save => kx-web/src/test/java/de/ipb_halle/kx/service}/FileAnalyserTest.java (97%) delete mode 100644 ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java rename ui/src/test/java/de/ipb_halle/lbac/collections/mock/{FileEntityServiceMock.java => FileObjectServiceMock.java} (72%) delete mode 100644 ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java rename ui/src/test/java/de/ipb_halle/lbac/file/mock/{FileEntityServiceMock.java => FileObjectServiceMock.java} (68%) diff --git a/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java index db4248855..8d3ca98e7 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/file/FileObjectService.java @@ -20,12 +20,21 @@ import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.kx.file.FileObjectEntity; import java.io.Serializable; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; - - +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.EntityType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,12 +43,136 @@ public class FileObjectService implements Serializable { private final static long serialVersionUID = 1L; - + + private final String DELETE_COLLECTION_FILES = "DELETE FROM files WHERE collection_id = :c"; + private final String COLLECTION_FILE_COUNT = "SELECT count(*) FROM files WHERE collection_id = :c"; + @PersistenceContext(name = "de.ipb_halle.lbac") private EntityManager em; private final Logger logger = LoggerFactory.getLogger(this.getClass()); - + + /** + * delete file entity + * + * @param fileObject - entity to delete + */ + public void delete(FileObject fileObject) { + this.em.remove(fileObject.createEntity()); + } + + /** + * delete all files of a collection + * @param collectionId collection Id + */ + @Deprecated + public void deleteCollectionFiles(Integer collectionId) { + this.em.createNativeQuery(DELETE_COLLECTION_FILES) + .setParameter("c", collectionId) + .executeUpdate(); + } + + /** + * get all file entities in collection + * + * @param collection - collection + * @return - list of file entities + */ + @Deprecated + public List getAllFilesInCollection(Integer collectionId) { + CriteriaBuilder builder = this.em.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createQuery(FileObjectEntity.class); + Root fileRoot = criteriaQuery.from(FileObjectEntity.class); + criteriaQuery.select(fileRoot); + criteriaQuery.where(builder.equal(fileRoot.get("collectionId"), collectionId)); + + List entities = this.em.createQuery(criteriaQuery).getResultList(); + List results = new ArrayList<>(); + + for (FileObjectEntity entity : entities) { + results.add(new FileObject(entity)); + } + return results; + } + + /** + * file count for collection + * + * @param collectionId - collection id + * @return number of documents or -1 + */ + public long getDocumentCount(Integer collectionId) { + try { + BigInteger cnt = (BigInteger) this.em.createNativeQuery(COLLECTION_FILE_COUNT) + .setParameter("c", collectionId) + .getSingleResult(); + return cnt.longValue(); + } catch (Exception e) { + this.logger.warn("getDocumentCount() caught an exception", e); + } + return -1; + } + + /** + * Convert String to lower case and add SQL wildcard padding to it. This is + * necessary as JPA2 does not provide means to create an ilike + * predicate. + * + * @param st input string + * @return "%" + st.toLowerCase() + "%" + */ + private String iWildcard(String st) { + return "%" + st.toLowerCase() + "%"; + } + + /** + * select data with params. + * + * @param cmap allowed id, name, filename, hash + * @return - list of file entities + */ + public List load(Map cmap) { + + CriteriaBuilder builder = this.em.getCriteriaBuilder(); + + CriteriaQuery criteriaQuery = builder.createQuery(FileObjectEntity.class); + Root fileObjectRoot = criteriaQuery.from(FileObjectEntity.class); + EntityType fileObjectType = em.getMetamodel().entity(FileObjectEntity.class); + + criteriaQuery.select(fileObjectRoot); + List predicates = new ArrayList<>(); + + if (cmap == null) { + cmap = new HashMap<>(); + } + if (cmap.get("id") != null) { + predicates.add(builder.equal(fileObjectRoot.get("id"), cmap.get("id"))); + } + if (cmap.get("name") != null) { + predicates.add(builder.like(builder.lower( + fileObjectRoot.get(fileObjectType.getDeclaredSingularAttribute("name", String.class))), + iWildcard((String) cmap.get("description")))); + } + if (cmap.get("filename") != null) { + predicates.add(builder.like(builder.lower( + fileObjectRoot.get(fileObjectType.getDeclaredSingularAttribute("filename", String.class))), + iWildcard((String) cmap.get("name")))); + } + if (cmap.get("hash") != null) { + predicates.add(builder.equal(fileObjectRoot.get("hash"), cmap.get("hash"))); + } + if (cmap.get("collection_id") != null) { + predicates.add(builder.equal(fileObjectRoot.get("collectionId"), cmap.get("collection_id"))); + } + criteriaQuery.where(builder.and(predicates.toArray(new Predicate[]{}))); + List results = new ArrayList<>(); + for (FileObjectEntity entity : this.em.createQuery(criteriaQuery).getResultList()) { + results.add(new FileObject(entity)); + } + return results; + } + + /** * get file entity by id * diff --git a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java index 4f9d4d54d..0fb617c01 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java @@ -19,11 +19,6 @@ import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.kx.file.FileObjectService; -/* -import de.ipb_halle.kx.termvector.StemmedWordOrigin; -import de.ipb_halle.kx.termvector.TermFrequency; -import de.ipb_halle.kx.termvector.TermVectorEntity; -*/ import java.io.Serializable; import java.util.ArrayList; @@ -64,6 +59,13 @@ public class TermVectorService implements Serializable { protected final String SQL_DELETE_ALL_TERMVECTORS = "DELETE from termvectors"; + private final String SQL_DELETE_TERMVECTORS_OF_COLLECTION + = "DELETE from termvectors AS tv ... " + + " WHERE fo.collection_id=:collectionId"; + + private final String SQL_DELETE_ORIGINAL_WORDS_OF_COLLECTION + = "DELETE FROM unstemmed_words AS stem ... "; + + " WHERE fo.collection_id=:collectionId"; @PersistenceContext(name = "de.ipb_halle.lbac") @@ -147,6 +149,21 @@ public int getSumOfAllWordsFromAllDocs() { } } + /** + * Delete all TermVectors for a given collectionId + * NOTE: Future implementations should not have references to + * Collections + */ + @Deprecated + public void deleteTermVectorsOfCollection(Integer collectionId) { + this.em.createNativeQuery(SQL_DELETE_TERMVECTORS_OF_COLLECTION) + .setParameter("collectionId", collectionId) + .executeUpdate(); + this.em.createNativeQuery(SQL_DELETE_ORIGINAL_WORDS_OF_COLLECTION) + .setParameter("collectionId", collectionId) + .executeUpdate(); + } + public void deleteTermVector(FileObject fileObject) { this.em.createNativeQuery(SQL_DELETE_UNSTEMMED_WORDS_BY_ID) diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java index b6178450e..f45215036 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java @@ -46,6 +46,9 @@ public class FileAnalyser { protected ParseTool parseTool = new ParseTool(); protected InputStream filterDefinition; + + private FileObjectService fileObjectService; + private Logger logger = LogManager.getLogger(this.getClass()); public FileAnalyser(InputStream filterDefinition) { @@ -90,8 +93,10 @@ protected List getWordOrigins() { List wordOrigins = new ArrayList<>(); @SuppressWarnings("unchecked") Map> map = (Map) parseTool.getFilterData().getValue(TermVectorFilter.STEM_DICT); - for (String s : map.keySet()) { - wordOrigins.add(new StemmedWordOrigin(s, map.get(s))); + for (String root : map.keySet()) { + for (String original : map.get(root)) { + wordOrigins.add(new StemmedWordOrigin(root, original)); + } } return wordOrigins; } diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java similarity index 97% rename from ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java rename to kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java index 9316f33d3..3c850f920 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileAnalyserTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java @@ -15,11 +15,11 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file.save; +package de.ipb_halle.kx.service; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.lbac.file.FilterDefinitionInputStreamFactory; -import de.ipb_halle.tx.file.StemmedWordOrigin; -import de.ipb_halle.tx.file.TermVector; import java.io.FileNotFoundException; import java.util.List; import java.util.Set; diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionBean.java b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionBean.java index 867987e30..26a2dae7e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionBean.java @@ -18,12 +18,12 @@ package de.ipb_halle.lbac.collections; import com.corejsf.util.Messages; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.ACObjectBean; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; - import de.ipb_halle.lbac.admission.LoginEvent; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.admission.ACList; @@ -31,7 +31,6 @@ import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.globals.ACObjectController; import de.ipb_halle.lbac.i18n.UIMessage; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.MemberService; @@ -110,7 +109,7 @@ public class CollectionBean implements Serializable, ACObjectBean { protected FileService fileService; @Inject - protected FileEntityService fileEntityService; + protected FileObjectService fileObjectService; @Inject protected GlobalAdmissionContext globalAdmissionContext; @@ -122,7 +121,7 @@ public class CollectionBean implements Serializable, ACObjectBean { protected ACListService acListService; @Inject - protected TermVectorEntityService termVectorEntityService; + protected TermVectorService termVectorService; @Override public void applyAclChanges() { @@ -159,12 +158,12 @@ public void initCollectionBean() { collectionOperation = new CollectionOperation( fileService, - fileEntityService, + fileObjectService, globalAdmissionContext, nodeService, collectionService, PUBLIC_COLLECTION_NAME, - termVectorEntityService); + termVectorService); collPermAnalyser = new CollectionPermissionAnalyser( PUBLIC_COLLECTION_NAME, diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOperation.java b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOperation.java index b8e2dc3ad..dd4cb2eb2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOperation.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOperation.java @@ -17,10 +17,10 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.service.NodeService; import java.io.Serializable; @@ -38,12 +38,12 @@ public class CollectionOperation implements Serializable { private final FileService fileService; - private final FileEntityService fileEntityService; + private final FileObjectService fileObjectService; private final GlobalAdmissionContext globalAdmissionContext; private final Logger LOGGER = LogManager.getLogger(CollectionOperation.class); private final NodeService nodeService; private final CollectionService collectionService; - private final TermVectorEntityService termVectorEntityService; + private final TermVectorService termVectorService; private final String PUBLIC_COLLECTION_NAME; private final String COLL_NOT_UNIQUE = "name %s for a new collection is not unique. (user: %s)"; private final String COLL_RESERVED = "name %s for a new collection is a reserved name. (user: %s)"; @@ -54,20 +54,20 @@ public class CollectionOperation implements Serializable { public CollectionOperation( FileService fileService, - FileEntityService fileEntityService, + FileObjectService fileObjectService, GlobalAdmissionContext globalAdmissionContext, NodeService nodeService, CollectionService collectionService, String publicCollectionName, - TermVectorEntityService termVectorEntityService) { + TermVectorService termVectorService) { this.fileService = fileService; - this.fileEntityService = fileEntityService; + this.fileObjectService = fileObjectService; this.globalAdmissionContext = globalAdmissionContext; this.nodeService = nodeService; this.collectionService = collectionService; this.PUBLIC_COLLECTION_NAME = publicCollectionName; - this.termVectorEntityService = termVectorEntityService; + this.termVectorService = termVectorService; } public enum OperationState { @@ -100,15 +100,16 @@ public OperationState clearCollection( fileService.deleteDir(activeCollection.getName()); } - termVectorEntityService.deleteTermVectorOfCollection(activeCollection); - fileEntityService.delete(activeCollection); - LOGGER.info(String.format("collection delete all file entities: %s:%s by %s", activeCollection.getName(), activeCollection.getIndexPath(), currentAccount.getLogin())); + throw new RuntimeException("xxxxx need to provide TermVector delete for entire collection"); +// termVectorService.deleteTermVectorOfCollection(activeCollection); +// fileEntityService.delete(activeCollection); +// LOGGER.info(String.format("collection delete all file entities: %s:%s by %s", activeCollection.getName(), activeCollection.getIndexPath(), currentAccount.getLogin())); } catch (Exception e) { LOGGER.error(ExceptionUtils.getStackTrace(e)); return OperationState.CLEAR_ERROR; } - return OperationState.OPERATION_SUCCESS; +// return OperationState.OPERATION_SUCCESS; } /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOrchestrator.java b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOrchestrator.java index 13334f3c7..c56132370 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOrchestrator.java +++ b/ui/src/main/java/de/ipb_halle/lbac/collections/CollectionOrchestrator.java @@ -17,11 +17,11 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.admission.MembershipService; @@ -69,7 +69,7 @@ public class CollectionOrchestrator implements Serializable { private CollectionService collectionService; @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; @Inject private MembershipService membershipService; @@ -120,7 +120,7 @@ public void startCollectionSearch( collectionService.load(new HashMap<>()), user); for(Collection col : readableList) { - col.setCountDocs(this.fileEntityService.getDocumentCount(col)); + col.setCountDocs(this.fileObjectService.getDocumentCount(col.getId())); } colState.getCollections().addAll(readableList); diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java index b8ad78cb6..be34f99dc 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteExec.java @@ -18,6 +18,7 @@ package de.ipb_halle.lbac.file; import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.service.FileService; import java.io.InputStream; @@ -37,16 +38,16 @@ public class FileDeleteExec implements Runnable { private FileObject fileObject; private Logger logger; - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; private FileService fs; - public FileDeleteExec(FileObject fo, FileEntityService fes, AsyncContext asyncContext) { + public FileDeleteExec(FileObject fo, FileObjectService fos, AsyncContext asyncContext) { this.logger = LogManager.getLogger(FileDeleteExec.class); this.asyncContext = asyncContext; this.fileObject = fo; - this.fileEntityService = fes; + this.fileObjectService = fos; fs = new FileService(); } @@ -74,7 +75,7 @@ public void run() { } try { - fileEntityService.delete(fileObject); + fileObjectService.delete(fileObject); this.logger.info(String.format("file %s in db deleted.", fileObject.getName())); } catch (Exception e) { this.logger.warn(String.format("Error deleting file entity %s in db.", fileObject.getName())); diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java index fb64e5550..aa7da00ff 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileDeleteWebService.java @@ -18,6 +18,7 @@ package de.ipb_halle.lbac.file; import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.CollectionService; import java.util.HashMap; @@ -46,7 +47,7 @@ public class FileDeleteWebService extends HttpServlet { private CollectionService collectionService; @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { @@ -72,10 +73,10 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { //*** getting fileEntity *** logger.info("file uuid: " + req.getPathInfo().substring(1)); cmap.put("id", req.getPathInfo().substring(1)); - List fe = this.fileEntityService.load(cmap); + List fe = this.fileObjectService.load(cmap); if ((fe != null) && (fe.size() > 0)) { - asyncContext.start(new FileDeleteExec(fe.get(0), fileEntityService, asyncContext)); + asyncContext.start(new FileDeleteExec(fe.get(0), fileObjectService, asyncContext)); } else { this.logger.warn("doDelete(): could not obtain fileEntity"); diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java deleted file mode 100644 index fdc403134..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileEntityService.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.file; - -import de.ipb_halle.kx.file.FileObject; -import de.ipb_halle.kx.file.FileObjectEntity; -import de.ipb_halle.kx.termvector.TermVector; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.admission.MemberService; -import java.io.Serializable; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.ejb.Stateless; -import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; -import javax.persistence.metamodel.EntityType; -import org.apache.commons.lang.exception.ExceptionUtils; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -@Stateless -public class FileEntityService implements Serializable { - - @PersistenceContext(name = "de.ipb_halle.lbac") - private EntityManager em; - - @Inject - private CollectionService collectionService; - - @Inject - private MemberService memberService; - - private Logger logger; - - public FileEntityService() { - logger = LogManager.getLogger(this.getClass().getName()); - } - - /** - * check file exists in collection - * - * @param hash - md5 hash - * @param collection - collection (id) - * @throws java.lang.Exception - */ - public void checkIfFileAlreadyExists(String hash, Collection collection) throws Exception { - boolean fileExists = ((BigInteger) this.em.createNativeQuery("SELECT count(*) FROM files WHERE hash = :hash and collection_id = :c") - .setParameter("hash", hash) - .setParameter("c", collection.getId()) - .getSingleResult()).longValue() > 0; - if (fileExists) { - throw new Exception("fileupload_error_duplicate_file"); - } - } - - /** - * delete file entity - * - * @param fileObject - entity to delete - */ - public void delete(FileObject fileObject) { - this.em.createNativeQuery("DELETE FROM files WHERE id=:id") - .setParameter("id", fileObject.getId()) - .executeUpdate(); - } - - /** - * delete all files owned by collection - * - * @param collection - collection - */ - public void delete(Collection collection) { - this.em.createNativeQuery("DELETE FROM files WHERE collection_id = :c") - .setParameter("c", collection.getId()) - .executeUpdate(); - } - - /** - * get all file entities in collection - * - * @param collection - collection - * @return - list of file entities - */ - public List getAllFilesInCollection(Collection collection) { - CriteriaBuilder builder = this.em.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = builder.createQuery(FileObjectEntity.class); - Root fileRoot = criteriaQuery.from(FileObjectEntity.class); - criteriaQuery.select(fileRoot); - criteriaQuery.where(builder.equal(fileRoot.get("collectionId"), collection.getId())); - - List entities = this.em.createQuery(criteriaQuery).getResultList(); - List results = new ArrayList<>(); - - for (FileObjectEntity entity : entities) { - results.add(new FileObject(entity)); - } - return results; - } - - /** - * file count for collection - * - * @param collection - collection (id) - * @return number of documents or -1 - */ - public long getDocumentCount(Collection collection) { - try { - BigInteger cnt = (BigInteger) this.em.createNativeQuery("SELECT count(*) FROM files WHERE collection_id = :c") - .setParameter("c", collection.getId()) - .getSingleResult(); - return cnt.longValue(); - } catch (Exception e) { - this.logger.warn("getDocumentCount() caught an exception", e); - } - return -1; - } - - /** - * get file entity by id - * - * @param id - id - * @return - file entity - */ - public FileObject getFileEntity(Integer id) { - FileObjectEntity entity = this.em.find(FileObjectEntity.class, id); - if (entity != null) { - return new FileObject(entity); - } - return null; - } - - /** - * Convert String to lower case and add SQL wildcard padding to it. This is - * necessary as JPA2 does not provide means to createCollection an ilike - * predicate. - * - * @param st input string - * @return "%" + st.toLowerCase() + "%" - */ - private String iWildcard(String st) { - return "%" + st.toLowerCase() + "%"; - } - - /** - * select data with params. - * - * @param cmap allowed id, name, filename, hash - * @return - list of file entities - */ - public List load(Map cmap) { - - CriteriaBuilder builder = this.em.getCriteriaBuilder(); - - CriteriaQuery criteriaQuery = builder.createQuery(FileObjectEntity.class); - Root fileObjectRoot = criteriaQuery.from(FileObjectEntity.class); - EntityType fileObjectType = em.getMetamodel().entity(FileObjectEntity.class); - - criteriaQuery.select(fileObjectRoot); - List predicates = new ArrayList<>(); - - if (cmap == null) { - cmap = new HashMap<>(); - } - if (cmap.get("id") != null) { - predicates.add(builder.equal(fileObjectRoot.get("id"), cmap.get("id"))); - } - if (cmap.get("name") != null) { - predicates.add(builder.like(builder.lower( - fileObjectRoot.get(fileObjectType.getDeclaredSingularAttribute("name", String.class))), - iWildcard((String) cmap.get("description")))); - } - if (cmap.get("filename") != null) { - predicates.add(builder.like(builder.lower( - fileObjectRoot.get(fileObjectType.getDeclaredSingularAttribute("filename", String.class))), - iWildcard((String) cmap.get("name")))); - } - if (cmap.get("hash") != null) { - predicates.add(builder.equal(fileObjectRoot.get("hash"), cmap.get("hash"))); - } - if (cmap.get("collection_id") != null) { - predicates.add(builder.equal(fileObjectRoot.get("collectionId"), cmap.get("collection_id"))); - } - criteriaQuery.where(builder.and(predicates.toArray(new Predicate[]{}))); - List results = new ArrayList<>(); - for (FileObjectEntity entity : this.em.createQuery(criteriaQuery).getResultList()) { - results.add(new FileObject(entity)); - } - return results; - } - - /** - * save entity - * - * @param fileObject - save entity - * @return - */ - public FileObject save(FileObject fileObject) { - FileObjectEntity foe = this.em.merge(fileObject.createEntity()); - fileObject.setId(foe.getId()); - return fileObject; - } - - /** - * - * @param vector - */ - public void saveTermVectors(List vector) { - try { - for (TermVector tv : vector) { - em.merge(tv.createEntity()); - } - } catch (Exception e) { - logger.error(ExceptionUtils.getStackTrace(e)); - } - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java index df72f4b0b..276e34144 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileSearchRequest.java @@ -18,7 +18,8 @@ package de.ipb_halle.lbac.file; import de.ipb_halle.kx.file.AttachmentHolder; -import de.ipb_halle.lbac.search.document.StemmedWordGroup; +import java.util.HashSet; +import java.util.Set; /** * @@ -27,7 +28,6 @@ public class FileSearchRequest { public AttachmentHolder holder; - public StemmedWordGroup wordsToSearchFor=new StemmedWordGroup(); - - + public Set wordsToSearchFor=new HashSet<> (); + } diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/FileUploadWebService.java b/ui/src/main/java/de/ipb_halle/lbac/file/FileUploadWebService.java index 3ab7cb0e0..6579dd8fb 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/FileUploadWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/FileUploadWebService.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.file; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.UserBean; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.collections.CollectionService; import java.io.IOException; import java.io.PrintWriter; @@ -43,7 +43,6 @@ public class FileUploadWebService extends HttpServlet { private final static long serialVersionUID = 1L; private final static long UPLOAD_TIMEOUT = 30L * 60L * 1000L; - private final String FILTER_DEFINITION = "fileParserFilterDefinition.json"; private final Logger logger = LogManager.getLogger(FileUploadWebService.class); @@ -51,10 +50,7 @@ public class FileUploadWebService extends HttpServlet { private CollectionService collectionService; @Inject - private FileEntityService fileEntityService; - - @Inject - private TermVectorEntityService termVectorEntityService; + private FileObjectService fileObjectService; @Inject private UserBean userBean; @@ -69,12 +65,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) { asyncContext.setTimeout(UPLOAD_TIMEOUT); asyncContext.start(new UploadToCol( - this.getClass().getResourceAsStream(FILTER_DEFINITION), - fileEntityService, + fileObjectService, userBean.getCurrentAccount(), asyncContext, - collectionService, - termVectorEntityService)); + collectionService)); } catch (Exception e) { logger.error(ExceptionUtils.getStackTrace(e)); if (asyncContext != null) { diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java index 51cb2d3f5..a57147db3 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java @@ -18,11 +18,11 @@ package de.ipb_halle.lbac.file; import de.ipb_halle.kx.file.AttachmentHolder; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.kx.termvector.StemmedWordOrigin; import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.save.FileAnalyser; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.file.save.FileSaver; @@ -57,27 +57,21 @@ public class UploadToCol implements Runnable { protected static final String HTTP_PARAMETER_COLLECTION = "collection"; protected String fileName; protected FileSaver fileSaver; - protected FileAnalyser fileAnalyser; - protected TermVectorService termVectorService; protected Integer fileId; - protected FileEntityService fileEntityService; + protected FileObjectService fileObjectService; private final Logger logger; public UploadToCol( - InputStream filterDefinition, - FileEntityService fileEntityService, + FileObjectService fileObjectService, User user, AsyncContext asyncContext, - CollectionService collectionService, - TermVectorService termVectorService) { - fileAnalyser = new FileAnalyser(filterDefinition); - fileSaver = new FileSaver(fileEntityService, user); + CollectionService collectionService) { + this.fileSaver = new FileSaver(fileObjectService, user); this.asyncContext = asyncContext; this.collectionService = collectionService; this.request = (HttpServletRequest) asyncContext.getRequest(); this.response = (HttpServletResponse) asyncContext.getResponse(); - this.termVectorService = termVectorService; - this.fileEntityService = fileEntityService; + this.fileObjectService = fileObjectService; this.logger = LogManager.getLogger(UploadToCol.class); } @@ -123,11 +117,12 @@ public void run() { getAttachmentTarget(), getFileNameFromRequest(), request.getPart(HTTP_PART_FILENAME).getInputStream()); - fileAnalyser.analyseFile(fileSaver.getFileLocation().toString(), fileId); - saveTermVector(fileAnalyser.getTermVector()); - saveOriginalWords(fileAnalyser.getWordOrigins()); - fileSaver.updateLanguageOfFile(fileAnalyser.getLanguage()); - response.getWriter().write(createJsonSuccessResponse(fileId, getFileNameFromRequest())); + + + throw new RuntimeException("xxxxx Need to do async call to KX-Web service"); + + +// response.getWriter().write(createJsonSuccessResponse(fileId, getFileNameFromRequest())); } catch (Exception e) { writeErrorMessage(e); logger.error(ExceptionUtils.getStackTrace(e)); @@ -137,14 +132,6 @@ public void run() { } - protected void saveOriginalWords(List originals) { - termVectorService.saveUnstemmedWordsOfDocument(originals, fileId); - } - - protected void saveTermVector(List termVector) { - fileEntityService.saveTermVectors(termVector); - } - private void writeErrorMessage(Exception outerException) { try (PrintWriter writer = response.getWriter()) { writer.write(createJsonErrorResponse(outerException.getMessage())); diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java index bd01292c0..98be10547 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/save/FileSaver.java @@ -19,8 +19,8 @@ import de.ipb_halle.kx.file.AttachmentHolder; import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.util.HexUtil; import java.io.File; import java.io.IOException; @@ -42,15 +42,15 @@ public class FileSaver { protected Path fileLocation; protected Integer fileId; protected AttachmentHolder objectOfAttachedFile; - protected FileEntityService fileEntityService; + protected FileObjectService fileObjectService; protected User user; protected String hash; protected static final String HASH_ALGO = "SHA-512"; protected Integer MAX_FILES_IN_FOLDER = 100; protected FileObject fileObject; - public FileSaver(FileEntityService fileEntityService, User user) { - this.fileEntityService = fileEntityService; + public FileSaver(FileObjectService fileObjectService, User user) { + this.fileObjectService = fileObjectService; this.user = user; } @@ -75,7 +75,7 @@ protected Integer saveFileInDB(AttachmentHolder objectOfAttachedFile, String fil fileObject.setUserId(user.getId()); fileObject.setHash(hash); fileObject.setCollectionId(objectOfAttachedFile.getId()); - fileObject = fileEntityService.save(fileObject); + fileObject = fileObjectService.save(fileObject); return fileObject.getId(); } @@ -89,7 +89,7 @@ protected Path calculateFileLocation(AttachmentHolder objectOfAttachedFile) { protected void updateFileInDB(Path fileName, String hash) { fileObject.setFileLocation(fileName.toString()); fileObject.setHash(hash); - fileEntityService.save(fileObject); + fileObjectService.save(fileObject); } protected void saveFileToFileSystem(InputStream inputStream, Path fileLocation) throws IOException, NoSuchAlgorithmException { @@ -106,7 +106,7 @@ protected void saveFileToFileSystem(InputStream inputStream, Path fileLocation) public void updateLanguageOfFile(String language) { fileObject.setDocumentLanguage(language); - fileEntityService.save(fileObject); + fileObjectService.save(fileObject); } public Path getFileLocation() { diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java b/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java index d1c406878..ab619662b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java @@ -17,12 +17,9 @@ */ package de.ipb_halle.lbac.search; -import de.ipb_halle.lbac.search.document.StemmedWordGroup; -import de.ipb_halle.tx.text.ParseTool; -import de.ipb_halle.tx.text.TextRecord; -import de.ipb_halle.tx.text.properties.Language; -import de.ipb_halle.tx.text.properties.TextProperty; -import de.ipb_halle.tx.text.properties.Word; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; /** * @@ -32,6 +29,12 @@ public class SearchQueryStemmer { protected String filterDefinition = "queryParserFilterDefinition.json"; + public Set stemmQuery(String queryString) { + return new HashSet (Arrays.asList("Not", "implemented", "yet")); + } + + + /* public StemmedWordGroup stemmQuery(String queryString) { StemmedWordGroup back = new StemmedWordGroup(); TextRecord tr = new TextRecord(queryString); @@ -53,4 +56,5 @@ public StemmedWordGroup stemmQuery(String queryString) { } return back; } +*/ } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java b/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java index ac9ada96e..45722a61e 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java @@ -29,7 +29,6 @@ import de.ipb_halle.lbac.material.structure.Structure; import de.ipb_halle.lbac.project.ProjectService; import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.search.document.StemmedWordGroup; import de.ipb_halle.lbac.service.NodeService; import java.util.Collections; import java.util.List; @@ -134,11 +133,13 @@ private void augmentDocumentSearchRequest( if (request.getSearchTarget() == SearchTarget.DOCUMENT) { Set materialNames = getNamesOfMaterials(result); + StringBuilder sb = new StringBuilder(); for (String name : materialNames) { - StemmedWordGroup swg = searchQueryStemmer.stemmQuery(name); - for (String stemmedName : swg.getAllStemmedWords()) { - request.addSearchCategory(SearchCategory.WORDROOT, stemmedName); - } + sb.append(name); + sb.append(" "); + } + for (String stem : searchQueryStemmer.stemmQuery(sb.toString())) { + request.addSearchCategory(SearchCategory.WORDROOT, stem); } } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchBean.java index 56fe64a2e..ad4527601 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchBean.java @@ -30,17 +30,19 @@ import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.SearchOrchestrator; +import de.ipb_halle.lbac.search.SearchQueryStemmer; import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.search.SearchService; import de.ipb_halle.lbac.search.SearchTarget; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.lbac.search.document.StemmedWordGroup; import de.ipb_halle.lbac.search.relevance.RelevanceCalculator; import de.ipb_halle.lbac.service.NodeService; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.enterprise.context.SessionScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; @@ -67,9 +69,8 @@ public class SearchBean implements Serializable { protected Logger logger = LogManager.getLogger(this.getClass().getName()); protected List shownObjects = new ArrayList<>(); protected SearchFilter searchFilter; - protected RelevanceCalculator relevanceCalculator = new RelevanceCalculator(new ArrayList<>()); + protected RelevanceCalculator relevanceCalculator = new RelevanceCalculator(new HashSet<>()); protected User currentUser; - StemmedWordGroup normalizedTerms; @Inject private MaterialOverviewBean materialBean; @@ -165,7 +166,8 @@ private List getDocumentsFromResults() { public void actionTriggerSearch() { shownObjects.clear(); - relevanceCalculator = new RelevanceCalculator(parseSearchTerms()); + Set terms = new SearchQueryStemmer().stemmQuery(searchFilter.getSearchTerms()); + relevanceCalculator = new RelevanceCalculator(terms); searchState = doSearch(); actionAddFoundObjectsToShownObjects(); } @@ -186,20 +188,6 @@ private SearchState doSearch() { return searchState; } - private List parseSearchTerms() { - List back = new ArrayList<>(); - if (searchFilter.getSearchTerms() != null) { - back = Arrays.asList(searchFilter.getSearchTerms() - .toLowerCase() - .replace("(", "") - .replace(")", "") - .replace(" or ", " ") - .trim() - .split(" ")); - } - return back; - } - public SearchFilter getSearchFilter() { return searchFilter; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchFilter.java b/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchFilter.java index 32a1e3870..ca1eae9fd 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchFilter.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/bean/SearchFilter.java @@ -152,8 +152,7 @@ public void init() { } private SearchRequest createDocumentRequest() { - SearchQueryStemmer searchQueryStemmer = new SearchQueryStemmer(); - Set normalizedTerms = searchQueryStemmer.stemmQuery(searchTerms.toLowerCase()).getAllStemmedWords(); + Set normalizedTerms = new SearchQueryStemmer().stemmQuery(searchTerms); DocumentSearchRequestBuilder docBuilder = new DocumentSearchRequestBuilder(user, 0, maxresults); docBuilder.setWordRoots(normalizedTerms); return docBuilder.build(); diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java index 5bd1c78d7..e61290aeb 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java @@ -20,13 +20,11 @@ import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.kx.file.FileObjectEntity; import de.ipb_halle.kx.termvector.TermFrequency; - -import de.ipb_halle.lbac.webclient.XmlSetWrapper; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.file.FileSearchRequest; import de.ipb_halle.lbac.search.SearchCategory; import de.ipb_halle.lbac.search.SearchQueryStemmer; @@ -38,7 +36,7 @@ import de.ipb_halle.lbac.search.lang.SqlBuilder; import de.ipb_halle.lbac.search.lang.Value; import de.ipb_halle.lbac.service.NodeService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; +import de.ipb_halle.lbac.webclient.XmlSetWrapper; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; @@ -69,9 +67,6 @@ public class DocumentSearchService { @PersistenceContext(name = "de.ipb_halle.lbac") private EntityManager em; - @Inject - private FileEntityService fileEntityService; - @Inject private NodeService nodeService; @@ -82,7 +77,7 @@ public class DocumentSearchService { private MemberService memberService; @Inject - private TermVectorEntityService termVectorEntityService; + private TermVectorService termVectorService; private final int MAX_TERMS = Integer.MAX_VALUE; @@ -136,7 +131,7 @@ public DocumentSearchState actionStartDocumentSearch( searchQueryStemmer = new SearchQueryStemmer(); // fetches all documents of the collection and adds the total // number of documents in the collection to the search state - StemmedWordGroup normalizedTerms = searchQueryStemmer.stemmQuery(searchText); + Set normalizedTerms = searchQueryStemmer.stemmQuery(searchText); searchState.setSearchWords(normalizedTerms); for (Collection coll : collsToSearchIn) { if (coll.getNode().equals(nodeService.getLocalNode())) { @@ -157,7 +152,7 @@ public DocumentSearchState actionStartDocumentSearch( TermOccurrence totalTerms = getTermOccurrence( docIds, - normalizedTerms.getAllStemmedWords()); + normalizedTerms); for (Document d : searchState.getFoundDocuments()) { d.setWordCount(getLengthOfDocument(d.getId())); @@ -181,7 +176,7 @@ public DocumentSearchState actionStartDocumentSearch( public TermOccurrence getTermOccurrence( List fileIds, Set searchTerms) { - TermOccurrence occurence = new TermOccurrence(); + TermOccurrence occurrence = new TermOccurrence(); for (Integer fileId : fileIds) { List frequencies = termVectorService.getTermFrequencies(fileId, searchTerms); for (TermFrequency freq : frequencies) { @@ -191,7 +186,6 @@ public TermOccurrence getTermOccurrence( return occurrence; } - private int loadTotalCountOfFiles() { Query q = em.createNativeQuery(SQL_LOAD_DOCUMENT_COUNT); @SuppressWarnings("unchecked") @@ -225,7 +219,7 @@ public SearchResult loadDocuments(SearchRequest request) { result.addResults(foundDocs); List docIds = getDocIds(foundDocs); - TermOccurrence totalTerms = getTermOccurence( + TermOccurrence totalTerms = getTermOccurrence( docIds, getWordRoots(request)); calculateWordCountOfDocs(foundDocs, totalTerms); @@ -276,7 +270,7 @@ private List getDocIds(List foundDocs) { * @return */ public long getSumOfWordsOfAllDocs() { - return termVectorEntityService.getSumOfAllWordsFromAllDocs(); + return termVectorService.getSumOfAllWordsFromAllDocs(); } private int getLengthOfDocument(int documentId) { @@ -325,8 +319,8 @@ public Set loadDocuments(FileSearchRequest request, int limit) { @SuppressWarnings("unchecked") List results = this.em.createNativeQuery(SQL_LOAD_DOCUMENTS, FileObjectEntity.class) .setParameter("collectionid", request.holder.getId()) - .setParameter("termvectorLength", request.wordsToSearchFor.getAllStemmedWords().size()) - .setParameter("termvector", request.wordsToSearchFor.getAllStemmedWords()) + .setParameter("termvectorLength", request.wordsToSearchFor.size()) + .setParameter("termvector", request.wordsToSearchFor) .getResultList(); int count = 0; @@ -356,25 +350,6 @@ private Document convertFileObjectToDocument(FileObject fo) { } - public String createSqlReplaceString(Map> stemmedWords) { - if (stemmedWords.isEmpty()) { - return ""; - } - List subClauses = new ArrayList<>(); - for (String word : stemmedWords.keySet()) { - List stemmedWordsWithQuotationMark = new ArrayList<>(); - for (String w : stemmedWords.get(word)) { - stemmedWordsWithQuotationMark.add("'" + w + "'"); - } - - subClauses.add( - String.format(" tv.wordroot IN (%s) ", - String.join(",", stemmedWordsWithQuotationMark))); - } - - return " AND (" + String.join(" OR ", subClauses) + ")"; - } - private EntityGraph createEntityGraph() { graphBuilder = new DocumentEntityGraphBuilder(); return graphBuilder.buildEntityGraph(true); diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchState.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchState.java index 7733a28c1..13bb95257 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchState.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchState.java @@ -39,7 +39,7 @@ public class DocumentSearchState implements Serializable { private List foundDocuments = Collections.synchronizedList(new ArrayList<>()); private Set unfinishedCollectionRequests = new HashSet<>(); private SearchStatistic stats = new SearchStatistic(); - private StemmedWordGroup searchWords=new StemmedWordGroup(); + private Set searchWords= new HashSet<> (); @PostConstruct public void init() { @@ -103,11 +103,11 @@ public void setStats(SearchStatistic stats) { this.stats = stats; } - public StemmedWordGroup getSearchWords() { + public Set getSearchWords() { return searchWords; } - public void setSearchWords(StemmedWordGroup searchWords) { + public void setSearchWords(Set searchWords) { this.searchWords = searchWords; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java index 121000892..fe825e0fd 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/TermOccurrence.java @@ -21,7 +21,7 @@ public Set getFileIds() { return termOccurences.keySet(); } - public void addOccurence(Integer fileId, String word, Integer amount) { + public void addOccurrence(Integer fileId, String word, Integer amount) { if (termOccurences.get(fileId) == null) { termOccurences.put(fileId, new HashMap<>()); } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java index 0f6564423..e14fd1135 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBean.java @@ -18,6 +18,7 @@ package de.ipb_halle.lbac.search.document.download; import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.ACListService; @@ -27,13 +28,23 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.SearchTarget; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.jsf.SendFileBean; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.io.File; import java.io.FileInputStream; @@ -63,7 +74,7 @@ public class DocumentDownloadBean { private NodeService nodeService; @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; @Inject private DocumentWebClient client; @@ -111,7 +122,7 @@ private InputStream downloadLocal(Document document) throws IOException { return null; } - FileObject fileObject = fileEntityService.getFileEntity(document.getId()); + FileObject fileObject = fileObjectService.loadFileObjectById(document.getId()); if ((fileObject == null) || (fileObject.getFileLocation() == null)) { return null; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java index ccaee3060..02182bda9 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/download/DocumentWebService.java @@ -18,6 +18,7 @@ package de.ipb_halle.lbac.search.document.download; import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.MemberService; @@ -25,7 +26,6 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.webservice.service.LbacWebService; import de.ipb_halle.lbac.webservice.service.NotAuthentificatedException; @@ -57,7 +57,7 @@ public class DocumentWebService extends LbacWebService { private Logger logger = LogManager.getLogger(this.getClass().getName()); @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; @Inject private CollectionService collectionService; @@ -85,7 +85,7 @@ public Response downloadDocument(DocumentWebRequest request) { return FORBIDDEN; } - FileObject fileObject = fileEntityService.getFileEntity(request.getFileObjectId()); + FileObject fileObject = fileObjectService.loadFileObjectById(request.getFileObjectId()); if ((fileObject == null) || (fileObject.getFileLocation() == null)) { return FILE_NOT_FOUND; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculator.java b/ui/src/main/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculator.java index 0b1abde4c..aa35fd62c 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculator.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculator.java @@ -17,17 +17,16 @@ */ package de.ipb_halle.lbac.search.relevance; -import de.ipb_halle.lbac.search.SearchQueryStemmer; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.lbac.search.document.StemmedWordGroup; import java.io.Serializable; import java.util.List; +import java.util.Set; /** * Implementation of the Okapi BM25 Ranking * {@link https://en.wikipedia.org/wiki/Okapi_BM25)} with an modification of the * inverse document frequency term to avoid negative weights function for - * calculating a relevanzfactor for documents. + * calculating a relevanceFactor for documents. * * @author fmauz */ @@ -35,13 +34,12 @@ public class RelevanceCalculator implements Serializable { private static final long serialVersionUID = 1L; - private StemmedWordGroup searchTerms; + private Set stemmedTerms; private final float k1 = 1.2f; private final float b = 0.75f; - private SearchQueryStemmer searchQueryStemmer = new SearchQueryStemmer(); - public RelevanceCalculator(List originalTerms) { - searchTerms = searchQueryStemmer.stemmQuery(String.join(" ", originalTerms)); + public RelevanceCalculator(Set terms) { + stemmedTerms = terms; } /** @@ -60,7 +58,7 @@ public List calculateRelevanceFactors( for (Document d : docsToUpdate) { d.setRelevance(0); - for (String word : searchTerms.getAllStemmedWords()) { + for (String word : stemmedTerms) { double docsWithHit = getDocAmountWithHit(docsToUpdate, word); if(docsWithHit==0){ continue; @@ -96,13 +94,8 @@ private double getDocAmountWithHit(List docsToUpdate, String term) { } return docs; } - - public StemmedWordGroup getSearchTerms() { - return searchTerms; - } - - public void setSearchTerms(StemmedWordGroup searchTerms) { - this.searchTerms = searchTerms; + + public void setStemmedTerms(Set terms) { + stemmedTerms = terms; } - } diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/ACListServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/ACListServiceTest.java index ebe9502f5..c83389c56 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/ACListServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/ACListServiceTest.java @@ -18,7 +18,6 @@ package de.ipb_halle.lbac.admission; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.collections.CollectionBean; @@ -27,7 +26,6 @@ import de.ipb_halle.lbac.collections.CollectionOrchestrator; import de.ipb_halle.lbac.collections.CollectionSearchState; import de.ipb_halle.lbac.collections.CollectionWebClient; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.CloudService; @@ -89,12 +87,10 @@ public static WebArchive createDeployment() { .addClass(Updater.class) .addClass(CollectionSearchState.class) .addClass(ACListService.class) - .addClass(FileEntityService.class) .addClass(FileService.class) .addClass(NodeService.class) .addClass(CollectionBean.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(MembershipOrchestrator.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/MemberServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/MemberServiceTest.java index c0a22f8ed..8cebb1afd 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/MemberServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/MemberServiceTest.java @@ -18,7 +18,6 @@ package de.ipb_halle.lbac.admission; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.CollectionBean; import de.ipb_halle.lbac.collections.CollectionOrchestrator; @@ -26,9 +25,7 @@ import de.ipb_halle.lbac.globals.KeyManager; import de.ipb_halle.lbac.collections.CollectionWebClient; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -80,11 +77,8 @@ public static WebArchive createDeployment() { .addClass(Updater.class) .addClass(CollectionSearchState.class) .addClass(ACListService.class) - .addClass(FileEntityService.class) - .addClass(FileService.class) .addClass(CollectionBean.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(MembershipOrchestrator.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupOrchestratorTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupOrchestratorTest.java index 738808fee..f4ad096d0 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupOrchestratorTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupOrchestratorTest.java @@ -25,9 +25,7 @@ import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.group.mock.DeactivateGroupWebClientMock; import de.ipb_halle.lbac.admission.group.mock.DeactivateGroupWebServiceMock; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -71,9 +69,7 @@ public static WebArchive createDeployment() { .addClass(DeactivateGroupWebClientMock.class) .addClass(Updater.class) .addClass(KeyManager.class) - .addClass(DeactivateGroupOrchestrator.class) - .addClass(FileService.class) - .addClass(FileEntityService.class); + .addClass(DeactivateGroupOrchestrator.class); } private Group createGroup() { diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebClientTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebClientTest.java index 4b0e1b08d..49c0e15ab 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebClientTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebClientTest.java @@ -24,9 +24,7 @@ import de.ipb_halle.lbac.admission.Group; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.group.mock.DeactivateGroupWebServiceMock; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -86,9 +84,7 @@ public static WebArchive createDeployment() { .addClass(WebRequestAuthenticator.class) .addClass(DeactivateGroupWebServiceMock.class) .addClass(Updater.class) - .addClass(KeyManager.class) - .addClass(FileService.class) - .addClass(FileEntityService.class); + .addClass(KeyManager.class); } private Group createGroup() { diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebServiceTest.java index 35c92b533..22530a687 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/group/DeactivateGroupWebServiceTest.java @@ -29,7 +29,6 @@ import de.ipb_halle.lbac.entity.Cloud; import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webclient.LbacWebClient; @@ -72,8 +71,7 @@ public static WebArchive createDeployment() { .addClass(FileService.class) .addClass(KeyManager.class) .addClass(DeactivateGroupWebService.class) - .addClass(KeyManager.class) - .addClass(FileEntityService.class); + .addClass(KeyManager.class); } @Test diff --git a/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebClientTest.java b/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebClientTest.java index 3bcdae037..eaf3f2899 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebClientTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebClientTest.java @@ -26,9 +26,7 @@ import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.admission.Group; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -62,10 +60,7 @@ public static WebArchive createDeployment() { .addClass(WebRequestAuthenticator.class) .addPackage(MembershipWebServiceMock.class.getPackage()) .addClass(Updater.class) - .addClass(KeyManager.class) - .addClass(FileService.class) - .addClass(FileEntityService.class); - + .addClass(KeyManager.class); } @Inject diff --git a/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebServiceTest.java index de520024f..f9d4029f1 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/announcement/membership/MembershipWebServiceTest.java @@ -34,9 +34,7 @@ import de.ipb_halle.lbac.service.NodeService; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.admission.Membership; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.util.ssl.SecureWebClientBuilder; import de.ipb_halle.lbac.webclient.LbacWebClient; import de.ipb_halle.lbac.webclient.WebRequestSignature; @@ -94,10 +92,7 @@ public static WebArchive createDeployment() { return prepareDeployment("MembershipWebServiceTest.war") .addClass(MembershipWebService.class) .addClass(WebRequestAuthenticator.class) - .addClass(FileService.class) - .addClass(KeyManager.class) - .addClass(KeyManager.class) - .addClass(FileEntityService.class); + .addClass(KeyManager.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java b/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java index cb51cc4b0..91426d7ca 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java @@ -17,15 +17,14 @@ */ package de.ipb_halle.lbac.base; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.file.FilterDefinitionInputStreamFactory; import de.ipb_halle.lbac.file.mock.AsyncContextMock; import de.ipb_halle.lbac.file.mock.UploadToColMock; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.NodeService; import java.io.File; import java.io.FileNotFoundException; @@ -42,22 +41,22 @@ public class DocumentCreator { protected AsyncContextMock asynContext; protected String exampleDocsRootFolder = "target/test-classes/exampledocs/"; - protected FileEntityService fileEntityService; + protected FileObjectService fileObjectService; protected CollectionService collectionService; protected NodeService nodeService; - protected TermVectorEntityService termVectorEntityService; + protected TermVectorService termVectorService; protected User user; public DocumentCreator( - FileEntityService fileEntityService, + FileObjectService fileObjectService, CollectionService collectionService, NodeService nodeService, - TermVectorEntityService termVectorEntityService) { + TermVectorService termVectorService) { - this.fileEntityService = fileEntityService; + this.fileObjectService = fileObjectService; this.collectionService = collectionService; this.nodeService = nodeService; - this.termVectorEntityService = termVectorEntityService; + this.termVectorService = termVectorService; } public Collection uploadDocuments(User user, String collectionName, String... files) throws FileNotFoundException, InterruptedException { @@ -88,12 +87,10 @@ private void uploadDocument(String documentName) throws FileNotFoundException, I new File(exampleDocsRootFolder + documentName), col.getName()); UploadToColMock upload = new UploadToColMock( - FilterDefinitionInputStreamFactory.getFilterDefinition(), - fileEntityService, + fileObjectService, user, asynContext, collectionService, - termVectorEntityService, "target/test-classes/collections"); upload.run(); diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java index 5bfad5ef4..17d0b53cf 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.lbac.base; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.admission.AdmissionSubSystemType; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; @@ -30,13 +32,11 @@ import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.mock.GlobalAdmissionContextMock; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.GlobalVersions; import de.ipb_halle.lbac.globals.KeyStoreFactory; import de.ipb_halle.lbac.material.CreationTools; import de.ipb_halle.lbac.material.mocks.MessagePresenterMock; import de.ipb_halle.lbac.project.Project; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.CloudNodeService; @@ -118,10 +118,10 @@ public class TestBase implements Serializable { protected NodeService nodeService; @Inject - protected TermVectorEntityService termVectorEntityService; + protected TermVectorService termVectorService; @Inject - protected FileEntityService fileEntityService; + protected FileObjectService fileObjectService; @Inject protected CollectionService collectionService; @@ -158,9 +158,9 @@ public static WebArchive prepareDeployment(String archiveName) { .addClass(MemberService.class) .addClass(MembershipService.class) .addClass(NodeService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(FileService.class) - .addClass(TermVectorEntityService.class) + .addClass(TermVectorService.class) .addClass(CollectionService.class) .addClass(ProjectService.class) .addClass(KeyStoreFactory.class) @@ -396,9 +396,9 @@ protected void createSolvents(String... solvents) { public void resetDB(MemberService memberService) { List colls = collectionService.load(new HashMap<>()); - termVectorEntityService.deleteTermVectors(); + termVectorService.deleteTermVectors(); for (Collection c : colls) { - fileEntityService.delete(c); + fileObjectService.deleteCollectionFiles(c.getId()); } } @@ -406,8 +406,8 @@ public void resetCollectionsInDb(CollectionService collectionService) { List colls = collectionService.load(null); for (Collection c : colls) { if (!c.getName().equals("public")) { - termVectorEntityService.deleteTermVectorOfCollection(c); - fileEntityService.delete(c); + termVectorService.deleteTermVectorsOfCollection(c); + fileObjectService.deleteCollectionFiles(c.getId()); collectionService.delete(c); } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionBeanTest.java index 79edf9e80..3aedbc5f4 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionBeanTest.java @@ -17,19 +17,19 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.LoginEvent; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.admission.MembershipOrchestrator; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.collections.mock.CollectionBeanMock; import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webservice.Updater; @@ -69,10 +69,10 @@ public void init() { bean = new CollectionBeanMock() .setCollectionService(collectionService) .setFileService(fileService) - .setFileEntityService(fileEntityService) + .setFileObjectService(fileObjectService) .setGlobalAdmissionContext(context) .setACListService(acListService) - .setTermVectorEntityService(termVectorEntityService) + .setTermVectorService(termVectorService) .setMemberService(memberService); bean.setCollectionOrchestrator(orchestrator); @@ -135,11 +135,11 @@ public static WebArchive createDeployment() { .addClass(Updater.class) .addClass(CollectionSearchState.class) .addClass(ACListService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(FileService.class) .addClass(CollectionBean.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) + .addClass(TermVectorService.class) .addClass(MembershipOrchestrator.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOperationTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOperationTest.java index 88cccd130..d851e1a6f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOperationTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOperationTest.java @@ -17,17 +17,17 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.mock.CollectionWebServiceMock; -import de.ipb_halle.lbac.collections.mock.FileEntityServiceMock; +import de.ipb_halle.lbac.collections.mock.FileObjectServiceMock; import de.ipb_halle.lbac.collections.mock.FileServiceMock; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.service.FileService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.util.HashMap; @@ -56,7 +56,7 @@ public class CollectionOperationTest extends TestBase { private final String PUBLIC_COLL_NAME = "public"; @Inject - private TermVectorEntityService termVectorEntityService; + private TermVectorService termVectorService; @Inject CollectionService collectionService; @@ -71,16 +71,16 @@ public void init() { Assert.assertNotNull("CollectionService not injected", collectionService); FileServiceMock fileServiceMock = new FileServiceMock(); - FileEntityServiceMock fileEntityMock = new FileEntityServiceMock(); + FileObjectServiceMock fileObjectServiceMock = new FileObjectServiceMock(); instance = new CollectionOperation( fileServiceMock, - fileEntityMock, + fileObjectServiceMock, globalAdmissionContext, nodeService, collectionService, PUBLIC_COLL_NAME, - termVectorEntityService); + termVectorService); resetDB(memberService); } @@ -191,12 +191,11 @@ public static WebArchive createDeployment() { .addClass(Navigator.class) .addClass(CollectionWebServiceMock.class) .addClass(CollectionSearchState.class) - .addClass(TermVectorEntityService.class) .addClass(EntityManager.class) .addClass(FileService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(CollectionService.class) - .addClass(TermVectorEntityService.class); + .addClass(TermVectorService.class); return UserBeanDeployment.add(deployment); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOrchestratorTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOrchestratorTest.java index ed3b2cbf4..2e3ebd88a 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOrchestratorTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionOrchestratorTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.TESTCLOUD; @@ -26,8 +27,6 @@ import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.collections.mock.CollectionWebClientMock; import de.ipb_halle.lbac.collections.mock.CollectionWebServiceMock; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -59,10 +58,9 @@ public static WebArchive createDeployment() { .addClass(Navigator.class) .addClass(WebRequestAuthenticator.class) .addClass(FileService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(CollectionOrchestrator.class) .addClass(CollectionWebClient.class) - .addClass(TermVectorEntityService.class) .addClass(CollectionWebServiceMock.class); return UserBeanDeployment.add(deployment); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionPermissionAnalyserTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionPermissionAnalyserTest.java index 7b30545c2..e300f9396 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionPermissionAnalyserTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionPermissionAnalyserTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.mock.CollectionWebServiceMock; @@ -24,9 +25,7 @@ import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.Group; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.navigation.Navigator; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.exp.ExperimentDeployment; @@ -69,12 +68,11 @@ public class CollectionPermissionAnalyserTest extends TestBase { public static WebArchive createDeployment() { WebArchive deployment = prepareDeployment("CollectionPermissionAnalyserTest.war") .addClass(FileService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(Navigator.class) .addClass(CollectionOrchestrator.class) .addClass(CollectionWebClient.class) .addClass(Updater.class) - .addClass(TermVectorEntityService.class) .addClass(SearchService.class) .addClass(ProjectService.class) .addClass(ExperimentService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionServiceTest.java index d0b8a90ff..128bf63f1 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionServiceTest.java @@ -17,10 +17,10 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.MembershipService; import de.ipb_halle.lbac.admission.MemberService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.admission.MembershipOrchestrator; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; @@ -28,7 +28,6 @@ import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.service.NodeService; @@ -85,11 +84,10 @@ public static WebArchive createDeployment() { .addClass(Updater.class) .addClass(CollectionSearchState.class) .addClass(ACListService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(FileService.class) .addClass(CollectionBean.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(MembershipOrchestrator.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebClientTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebClientTest.java index 5231cd318..d7023e55f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebClientTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebClientTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.mock.CollectionWebServiceMock; @@ -24,7 +25,6 @@ import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webclient.WebRequestSignature; @@ -80,7 +80,7 @@ public static WebArchive createDeployment() { .addClass(CollectionWebClient.class) .addClass(CollectionWebServiceMock.class) .addClass(FileService.class) - .addClass(FileEntityService.class); + .addClass(FileObjectService.class); return UserBeanDeployment.add(deployment); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebServiceTest.java index 362edc70a..b2f8c81c4 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/CollectionWebServiceTest.java @@ -17,12 +17,12 @@ */ package de.ipb_halle.lbac.collections; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.mock.CollectionWebServiceMock; import de.ipb_halle.lbac.entity.CloudNode; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.admission.MembershipService; @@ -31,7 +31,6 @@ import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.webclient.LbacWebClient; import de.ipb_halle.lbac.webclient.WebRequestSignature; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -85,8 +84,7 @@ public static WebArchive createDeployment() { .addClass(WebRequestAuthenticator.class) .addClass(CollectionWebServiceMock.class) .addClass(FileService.class) - .addClass(TermVectorEntityService.class) - .addClass(FileEntityService.class); + .addClass(FileObjectService.class); return ItemDeployment.add(ExperimentDeployment.add(UserBeanDeployment.add(deployment))); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/mock/CollectionBeanMock.java b/ui/src/test/java/de/ipb_halle/lbac/collections/mock/CollectionBeanMock.java index bd4b882e2..f70b65dd8 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/mock/CollectionBeanMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/mock/CollectionBeanMock.java @@ -17,13 +17,13 @@ */ package de.ipb_halle.lbac.collections.mock; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.collections.CollectionBean; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.FileService; /** @@ -49,8 +49,8 @@ public CollectionBeanMock setFileService(FileService service) { return this; } - public CollectionBeanMock setFileEntityService(FileEntityService service) { - this.fileEntityService = service; + public CollectionBeanMock setFileObjectService(FileObjectService service) { + this.fileObjectService = service; return this; } @@ -64,8 +64,8 @@ public CollectionBeanMock setACListService(ACListService acListService) { return this; } - public CollectionBeanMock setTermVectorEntityService(TermVectorEntityService service) { - this.termVectorEntityService = service; + public CollectionBeanMock setTermVectorService(TermVectorService service) { + this.termVectorService = service; return this; } diff --git a/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java b/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileObjectServiceMock.java similarity index 72% rename from ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java rename to ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileObjectServiceMock.java index 3c125bfe8..93980ae6a 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileEntityServiceMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/collections/mock/FileObjectServiceMock.java @@ -17,10 +17,10 @@ */ package de.ipb_halle.lbac.collections.mock; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.tx.file.FileObject; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -29,26 +29,17 @@ * * @author fmauz */ -public class FileEntityServiceMock extends FileEntityService { +public class FileObjectServiceMock extends FileObjectService { private boolean deleteSuccess = true; private boolean fileAvailable = true; - public void FileEntityServiceInit() { - - } - public Boolean delete(User user) { return deleteSuccess; } @Override - public List getAllFilesInCollection(Collection collection) { - return new ArrayList<>(); - } - - @Override - public FileObject getFileEntity(Integer id) { + public FileObject loadFileObjectById(Integer id) { return new FileObject(); } @@ -56,11 +47,6 @@ public Boolean isFileAvailable(String hash) { return fileAvailable; } - @Override - public void checkIfFileAlreadyExists(String hash, Collection collection) throws Exception { - - } - @Override public List load(Map cmap) { return new ArrayList<>(); @@ -72,8 +58,7 @@ public FileObject save(FileObject fileEntity) { } @Override - public void delete(Collection collection) { + public void deleteCollectionFiles(Integer collectionId) { } - } diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java deleted file mode 100644 index 628a16a74..000000000 --- a/ui/src/test/java/de/ipb_halle/lbac/file/FileEntityServiceTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.file; - -import de.ipb_halle.lbac.base.TestBase; -import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; -import de.ipb_halle.lbac.admission.ACList; -import de.ipb_halle.lbac.admission.ACPermission; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.service.FileService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.testcontainers.PostgresqlContainerExtension; -import de.ipb_halle.tx.file.TermVector; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit5.ArquillianExtension; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.junit.Assert.assertEquals; - -/** - * - * @author fmauz - */ -@ExtendWith(PostgresqlContainerExtension.class) -@ExtendWith(ArquillianExtension.class) -public class FileEntityServiceTest extends TestBase { - - @Inject - private CollectionService collectionService; - - @Inject - private FileEntityService fileEntityService; - - @Inject - private TermVectorEntityService termVectorEntityService; - - @Deployment - public static WebArchive createDeployment() { - return prepareDeployment("FileEntityServiceTest.war") - .addClass(FileEntityService.class) - .addClass(FileService.class) - .addClass(FileObject.class) - .addClass(TermVectorEntityService.class) - .addClass(CollectionService.class); - - } - - @PersistenceContext(name = "de.ipb_halle.lbac") - private EntityManager em; - - @Test - public void testSave() { - - User u = createUser( - "testuser", - "testuser"); - - ACList acl = new ACList(); - acl.setName("test"); - acl.addACE(u, ACPermission.values()); - - Collection col = new Collection(); - col.setNode(this.nodeService.getLocalNode()); - col.setName("Test_Collection1"); - col.setDescription("Test_Collection1_Description"); - col.setIndexPath("/doc/test.pdf"); - col.setACList(acl); - col.setOwner(u); - - col=collectionService.save(col); - - FileObject fE = new FileObject(); - fE.setCollectionId(col.getId()); - fE.setCreated(new Date()); - fE.setDocumentLanguage("en"); - fE.setFileLocation("testFile.pdf"); - fE.setHash("testHash"); - fE.setName("testFile"); - fE.setUserId(u.getId()); - - fE=fileEntityService.save(fE); - - TermVector tv = new TermVector("testWord", fE.getId(), 3); - fileEntityService.saveTermVectors(Arrays.asList(tv)); - List ids = new ArrayList<>(); - ids.add(fE.getId()); - termVectorEntityService.getTermVector(ids, 10); - - int sumOfWords = termVectorEntityService.getSumOfAllWordsFromAllDocs(); - - List lfo = fileEntityService.getAllFilesInCollection(col); - assertEquals("Found one file", 1, lfo.size()); - assertEquals("Filename of file matches", "testFile.pdf", lfo.get(0).getFileLocation()); - assertEquals("Document count matches", 1, fileEntityService.getDocumentCount(col)); - } -} diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java index 99771c2f9..aa81b6c25 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java @@ -17,6 +17,9 @@ */ package de.ipb_halle.lbac.file; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermFrequency; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.base.TestBase; @@ -27,11 +30,11 @@ import de.ipb_halle.lbac.file.mock.HttpServletResponseMock.WriterMock; import de.ipb_halle.lbac.file.mock.UploadToColMock; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; -import de.ipb_halle.tx.file.FileObject; import java.io.File; import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.openejb.loader.Files; @@ -56,7 +59,7 @@ public class UploadToColTest extends TestBase { private CollectionService collectionService; @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; protected String examplaDocsRootFolder = "target/test-classes/exampledocs/"; protected User publicUser; @@ -82,31 +85,28 @@ public void cleanUp() { public void test001_fileUploadTest() throws Exception { createAndSaveNewCol(); UploadToColMock upload = new UploadToColMock( - FilterDefinitionInputStreamFactory.getFilterDefinition(), - fileEntityService, + fileObjectService, publicUser, new AsyncContextMock( new File(examplaDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), col.getName()), collectionService, - termVectorEntityService, "target/test-classes/collections"); upload.run(); Map cmap = new HashMap<>(); cmap.put("id", upload.fileId); - FileObject file = fileEntityService.load(cmap).get(0); + FileObject file = fileObjectService.load(cmap).get(0); Assert.assertNotNull(file); - Assert.assertTrue(!termVectorEntityService.getTermVector(Arrays.asList(upload.fileId), 10).isEmpty()); + Assert.assertTrue(!termVectorService.getTermVector(Arrays.asList(upload.fileId), 10).isEmpty()); Assert.assertEquals( "informatik", - termVectorEntityService.loadUnstemmedWordsOfDocument( + termVectorService.loadUnstemmedWordsOfDocument( upload.fileId, "informat").get(0) - .getOriginalWord() - .iterator() - .next()); + .getOriginalWord()); + WriterMock writermock = ((WriterMock) upload.response.getWriter()); String json = writermock.getJson(); @@ -117,14 +117,12 @@ public void test001_fileUploadTest() throws Exception { @Test public void test002_fileUploadTestNoCollectionFound() throws Exception { UploadToColMock upload = new UploadToColMock( - FilterDefinitionInputStreamFactory.getFilterDefinition(), - fileEntityService, + fileObjectService, publicUser, new AsyncContextMock( new File(examplaDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), "test-coll-does-not-exist"), collectionService, - termVectorEntityService, "target/test-classes/collections"); upload.run(); @@ -138,18 +136,16 @@ public void test002_fileUploadTestNoCollectionFound() throws Exception { public void test003_fileUploadWithSmallNumbers() throws Exception { createAndSaveNewCol(); UploadToColMock upload = new UploadToColMock( - FilterDefinitionInputStreamFactory.getFilterDefinition(), - fileEntityService, + fileObjectService, publicUser, new AsyncContextMock( new File(examplaDocsRootFolder + "ShotNumberExample.docx"), col.getName()), collectionService, - termVectorEntityService, "target/test-classes/collections"); upload.run(); - Map terms = termVectorEntityService.getTermVector(Arrays.asList(upload.fileId), 100); + List terms = termVectorService.getTermVector(Arrays.asList(upload.fileId), 100); Assert.assertEquals(4, terms.size()); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java b/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileObjectServiceMock.java similarity index 68% rename from ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java rename to ui/src/test/java/de/ipb_halle/lbac/file/mock/FileObjectServiceMock.java index ed8a7a3b8..67b7080d0 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileEntityServiceMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/mock/FileObjectServiceMock.java @@ -17,23 +17,17 @@ */ package de.ipb_halle.lbac.file.mock; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.tx.file.FileObject; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; /** * * @author fmauz */ -public class FileEntityServiceMock extends FileEntityService { +public class FileObjectServiceMock extends FileObjectService { @Override - public void checkIfFileAlreadyExists(String hash, Collection collection) throws Exception { - - } - - @Override - public FileObject save(FileObject fileEntity) { + public FileObject save(FileObject fileObject) { return null; } diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java b/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java index bec6644db..4532a6859 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/mock/UploadToColMock.java @@ -17,13 +17,13 @@ */ package de.ipb_halle.lbac.file.mock; +import de.ipb_halle.kx.file.AttachmentHolder; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.file.UploadToCol; -import de.ipb_halle.tx.file.AttachmentHolder; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import java.io.InputStream; import javax.servlet.AsyncContext; @@ -36,14 +36,12 @@ public class UploadToColMock extends UploadToCol { protected String fileBaseFolder; public UploadToColMock( - InputStream filterDefinition, - FileEntityService fileEntityService, + FileObjectService fileObjectService, User user, AsyncContext asyncContext, CollectionService collectionService, - TermVectorEntityService termVectorService, String fileBaseFolder) { - super(filterDefinition, fileEntityService, user, asyncContext, collectionService, termVectorService); + super(fileObjectService, user, asyncContext, collectionService); this.fileBaseFolder = fileBaseFolder; } diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java index 5ad91a1cc..2dbb3b017 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/save/FileSaverTest.java @@ -17,16 +17,16 @@ */ package de.ipb_halle.lbac.file.save; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.File; import java.io.FileInputStream; @@ -59,7 +59,7 @@ public class FileSaverTest extends TestBase { private CollectionService collectionService; @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; private String exampleDocsRootFolder = "target/test-classes/exampledocs/"; @@ -79,7 +79,7 @@ public void cleanUp() { @Test public void test001_saveDocumentToCollection() throws FileNotFoundException, NoSuchAlgorithmException, IOException { - FileSaver fileSaver = new FileSaver(fileEntityService, publicUser); + FileSaver fileSaver = new FileSaver(fileObjectService, publicUser); col = new Collection(); col.setACList(GlobalAdmissionContext.getPublicReadACL()); @@ -96,10 +96,10 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS FileInputStream stream = new FileInputStream(f); Integer id = fileSaver.saveFile(col, "Document1.pdf", stream); - Assert.assertEquals(1, fileEntityService.getDocumentCount(col)); + Assert.assertEquals(1, fileObjectService.getDocumentCount(col.getId())); Map cmap = new HashMap<>(); cmap.put("id", id); - FileObject fo = fileEntityService.load(cmap).get(0); + FileObject fo = fileObjectService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("en", fo.getDocumentLanguage()); Assert.assertEquals("Document1.pdf", fo.getName()); @@ -108,9 +108,9 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS f = new File(exampleDocsRootFolder + "DocumentX.docx"); stream = new FileInputStream(f); id = fileSaver.saveFile(col, "DocumentX.docx", stream); - Assert.assertEquals(2, fileEntityService.getDocumentCount(col)); + Assert.assertEquals(2, fileObjectService.getDocumentCount(col.getId())); cmap.put("id", id); - fo = fileEntityService.load(cmap).get(0); + fo = fileObjectService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("en", fo.getDocumentLanguage()); Assert.assertEquals("DocumentX.docx", fo.getName()); @@ -119,9 +119,9 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS f = new File(exampleDocsRootFolder + "TestTabelle.xlsx"); stream = new FileInputStream(f); id = fileSaver.saveFile(col, "TestTabelle.xlsx", stream); - Assert.assertEquals(3, fileEntityService.getDocumentCount(col)); + Assert.assertEquals(3, fileObjectService.getDocumentCount(col.getId())); cmap.put("id", id); - fo = fileEntityService.load(cmap).get(0); + fo = fileObjectService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("en", fo.getDocumentLanguage()); Assert.assertEquals("TestTabelle.xlsx", fo.getName()); @@ -131,9 +131,9 @@ public void test001_saveDocumentToCollection() throws FileNotFoundException, NoS stream = new FileInputStream(f); id = fileSaver.saveFile(col, "TestTabelle.xlsx", stream); fileSaver.updateLanguageOfFile("de"); - Assert.assertEquals(4, fileEntityService.getDocumentCount(col)); + Assert.assertEquals(4, fileObjectService.getDocumentCount(col.getId())); cmap.put("id", id); - fo = fileEntityService.load(cmap).get(0); + fo = fileObjectService.load(cmap).get(0); Assert.assertEquals(col.getId(), fo.getCollectionId()); Assert.assertEquals("de", fo.getDocumentLanguage()); Assert.assertEquals("a9eed28584c7e6df1d061c77884820524a7d2b4c6644ef5d13b0c2daedaf4d10d040b7c7380df448f91a28eb7fba94cf0b4a964ae141032c63a0b571aeaa5ccf", fo.getHash()); diff --git a/ui/src/test/java/de/ipb_halle/lbac/globals/KeyManagerTest.java b/ui/src/test/java/de/ipb_halle/lbac/globals/KeyManagerTest.java index 393be8a34..19dfbc963 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/globals/KeyManagerTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/globals/KeyManagerTest.java @@ -20,10 +20,8 @@ import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.collections.CollectionWebService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.CloudNodeService; -import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -59,8 +57,6 @@ public static WebArchive createDeployment() { .addClass(CloudNodeService.class) .addClass(NodeService.class) .addClass(WebRequestAuthenticator.class) - .addClass(FileService.class) - .addClass(FileEntityService.class) .addClass(KeyManager.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/material/common/bean/save/MaterialEditSaverTest.java b/ui/src/test/java/de/ipb_halle/lbac/material/common/bean/save/MaterialEditSaverTest.java index 058d75394..f78706389 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/material/common/bean/save/MaterialEditSaverTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/material/common/bean/save/MaterialEditSaverTest.java @@ -17,41 +17,24 @@ */ package de.ipb_halle.lbac.material.common.bean.save; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; -import de.ipb_halle.lbac.collections.CollectionBean; -import de.ipb_halle.lbac.collections.CollectionOrchestrator; -import de.ipb_halle.lbac.collections.CollectionWebClient; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACPermission; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.material.CreationTools; import de.ipb_halle.lbac.material.Material; -import de.ipb_halle.lbac.material.biomaterial.TaxonomyNestingService; import de.ipb_halle.lbac.material.common.StorageClass; import de.ipb_halle.lbac.material.common.StorageCondition; -import de.ipb_halle.lbac.material.common.entity.index.MaterialIndexHistoryEntity; import de.ipb_halle.lbac.material.common.service.MaterialService; import de.ipb_halle.lbac.material.mocks.StructureInformationSaverMock; -import de.ipb_halle.lbac.material.biomaterial.TaxonomyService; -import de.ipb_halle.lbac.material.biomaterial.TissueService; -import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.project.Project; import de.ipb_halle.lbac.project.ProjectService; import de.ipb_halle.lbac.project.ProjectType; -import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; -import de.ipb_halle.lbac.search.wordcloud.WordCloudBean; -import de.ipb_halle.lbac.search.wordcloud.WordCloudWebClient; import de.ipb_halle.lbac.admission.ACListService; -import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.material.MaterialDeployment; -import de.ipb_halle.lbac.service.FileService; -import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.util.HashMap; import java.util.List; @@ -109,8 +92,8 @@ public void init() { projectService.saveProjectToDb(p); mOld = creationTools.createEmptyStructure(p.getId()); mOld.getStorageInformation().setStorageClass(new StorageClass(1, "storageClass-1")); - - + + materialService.setStructureInformationSaver(new StructureInformationSaverMock()); } @@ -149,7 +132,7 @@ public void test001_saveStorageDiffs() throws Exception { Assert.assertEquals("Testcase 001 - Second new condition must be acid sensitive", (Integer) StorageCondition.acidSensitive.getId(), newStorageConditions.get(1)); Assert.assertEquals("Testcase 001 - Third new condition must be keep cool", (Integer) StorageCondition.keepCool.getId(), newStorageConditions.get(2)); - // Check the history of the storage classes + // Check the history of the storage classes List storageClassHist = (List) entityManagerService.doSqlQuery("select description_old,description_new,storageclass_old, storageclass_new from storages_hist where id=" + mNew.getId()); Assert.assertTrue("Testcase 001 - history of storageclass must not be empty", !storageClassHist.isEmpty()); Assert.assertNull("Testcase 001 - old remarks of storage class must be null", storageClassHist.get(0)[0]); @@ -188,7 +171,7 @@ public void test002_saveStorageConditionsDiffsWithoutStorageClassDiffs() throws Assert.assertEquals("Testcase 002 - First new condition must be light sensitive", (Integer) StorageCondition.lightSensitive.getId(), newStorageConditions.get(0)); Assert.assertEquals("Testcase 002 - Second new condition must be acid sensitive", (Integer) StorageCondition.acidSensitive.getId(), newStorageConditions.get(1)); - // Check the history of the storage classes + // Check the history of the storage classes List storageClassHist = (List) entityManagerService.doSqlQuery("select description_old,description_new,storageclass_old, storageclass_new from storages_hist where id=" + mNew.getId()); Assert.assertTrue("Testcase 002 - history of storageclass must be empty", storageClassHist.isEmpty()); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchOrchestratorTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchOrchestratorTest.java index 7ee0b4dab..4bde557ca 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchOrchestratorTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/SearchOrchestratorTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; @@ -29,7 +30,6 @@ import de.ipb_halle.lbac.exp.ExperimentService; import de.ipb_halle.lbac.exp.assay.AssayService; import de.ipb_halle.lbac.exp.text.TextService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.service.ArticleService; import de.ipb_halle.lbac.material.biomaterial.TaxonomyNestingService; @@ -39,7 +39,6 @@ import de.ipb_halle.lbac.search.bean.SearchState; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.search.mocks.SearchWebClientMock; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -119,9 +118,8 @@ public static WebArchive createDeployment() { .addClass(TaxonomyService.class) .addClass(TissueService.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(CollectionService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(ExperimentService.class) .addClass(ExpRecordService.class) .addClass(SearchOrchestrator.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java index 96ed25da8..f6e63f29f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java @@ -21,8 +21,7 @@ * * @author fmauz */ -import de.ipb_halle.lbac.search.document.StemmedWordGroup; -import java.io.FileNotFoundException; +import java.util.Set; import org.junit.Assert; import org.junit.jupiter.api.Test; @@ -30,17 +29,12 @@ public class SearchQueryStemmerTest { @Test - public void test001_stemmQuery() throws FileNotFoundException { + public void test001_stemmQuery() { SearchQueryStemmer sqs=new SearchQueryStemmer(); - StemmedWordGroup results=sqs.stemmQuery("Werkzeuge gebrauchen"); - Assert.assertTrue(results.getStemmedWordsFor("Werkzeuge").contains("werkzeug")); - Assert.assertTrue(results.getStemmedWordsFor("gebrauchen").contains("gebrauch")); - Assert.assertEquals(sqs.stemmQuery("").getStemmedWordsFor("").size(), 0); - results=sqs.stemmQuery("*"); - results=sqs.stemmQuery(" "); - results=sqs.stemmQuery("a"); - results=sqs.stemmQuery("\n \r"); - int i=0; + Set results=sqs.stemmQuery("Werkzeuge gebrauchen"); + Assert.assertTrue(results.contains("werkzeug")); + Assert.assertTrue(results.contains("gebrauch")); + Assert.assertEquals(sqs.stemmQuery("").size(), 0); } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java index f71d98ea5..a50437f4a 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.lbac.search; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.items.service.*; @@ -44,7 +46,6 @@ import de.ipb_halle.lbac.exp.assay.AssayService; import de.ipb_halle.lbac.exp.search.ExperimentSearchRequestBuilder; import de.ipb_halle.lbac.exp.text.TextService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.items.Item; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.search.ItemSearchRequestBuilder; @@ -69,9 +70,7 @@ import de.ipb_halle.lbac.project.ProjectSearchRequestBuilder; import de.ipb_halle.lbac.project.ProjectService; import de.ipb_halle.lbac.search.document.DocumentSearchRequestBuilder; - import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.FileNotFoundException; import java.nio.file.Paths; @@ -547,10 +546,10 @@ private void uploadDocuments() { publicUser = memberService.loadUserById(GlobalAdmissionContext.PUBLIC_ACCOUNT_ID); DocumentCreator documentCreator = new DocumentCreator( - fileEntityService, + fileObjectService, collectionService, nodeService, - termVectorEntityService); + termVectorService); try { Collection col = documentCreator.uploadDocuments( @@ -790,9 +789,9 @@ public static WebArchive createDeployment() { .addClass(TaxonomyService.class) .addClass(TissueService.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) + .addClass(TermVectorService.class) .addClass(CollectionService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(ExperimentService.class) .addClass(ExpRecordService.class) .addClass(AssayService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebClientTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebClientTest.java index 15117cf21..f80d3b764 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebClientTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebClientTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; @@ -31,7 +32,6 @@ import de.ipb_halle.lbac.exp.RemoteExperiment; import de.ipb_halle.lbac.exp.assay.AssayService; import de.ipb_halle.lbac.exp.text.TextService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.RemoteItem; import de.ipb_halle.lbac.material.MaterialType; @@ -39,7 +39,6 @@ import de.ipb_halle.lbac.search.bean.SearchFilter; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.search.mocks.SearchWebServiceMock; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.File; @@ -147,9 +146,8 @@ public static WebArchive createDeployment() { WebArchive deployment = prepareDeployment("SearchWebClientTest.war") .addClass(SearchService.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(CollectionService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(ExperimentService.class) .addClass(SearchWebService.class) .addClass(WebRequestAuthenticator.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebServiceTest.java index e47f39550..8188bcd85 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/SearchWebServiceTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; @@ -30,7 +31,6 @@ import de.ipb_halle.lbac.exp.ExperimentService; import de.ipb_halle.lbac.exp.assay.AssayService; import de.ipb_halle.lbac.exp.text.TextService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.globals.KeyManager; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.RemoteItem; @@ -40,7 +40,6 @@ import de.ipb_halle.lbac.project.Project; import de.ipb_halle.lbac.project.ProjectService; import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.webclient.LbacWebClient; import de.ipb_halle.lbac.webclient.WebRequestSignature; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; @@ -207,9 +206,8 @@ public static WebArchive createDeployment() { WebArchive deployment = prepareDeployment("SearchWebServiceTest.war") .addClass(SearchService.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(CollectionService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(SearchWebService.class) .addClass(WebRequestAuthenticator.class); return ExperimentDeployment.add(ItemDeployment.add(UserBeanDeployment.add(deployment))); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/bean/NetObjectPresenterTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/bean/NetObjectPresenterTest.java index 2979fcca4..7326fd6fc 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/bean/NetObjectPresenterTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/bean/NetObjectPresenterTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.bean; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; @@ -26,7 +27,6 @@ import de.ipb_halle.lbac.exp.ExperimentService; import de.ipb_halle.lbac.exp.assay.AssayService; import de.ipb_halle.lbac.exp.text.TextService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.service.ArticleService; import de.ipb_halle.lbac.material.MaterialDeployment; @@ -38,7 +38,6 @@ import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.SearchService; import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.util.List; import org.jboss.arquillian.container.test.api.Deployment; @@ -175,9 +174,8 @@ public static WebArchive createDeployment() { .addClass(TaxonomyService.class) .addClass(TissueService.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(CollectionService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(ExpRecordService.class) .addClass(AssayService.class) .addClass(TextService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java index e23878f56..bdd0a0a4d 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.bean; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.LoginEvent; import de.ipb_halle.lbac.admission.User; @@ -34,7 +35,6 @@ import de.ipb_halle.lbac.exp.ExperimentService; import de.ipb_halle.lbac.exp.assay.AssayService; import de.ipb_halle.lbac.exp.text.TextService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.service.ArticleService; import de.ipb_halle.lbac.material.biomaterial.TaxonomyNestingService; @@ -47,7 +47,6 @@ import de.ipb_halle.lbac.search.SearchService; import de.ipb_halle.lbac.search.SearchWebClient; import de.ipb_halle.lbac.search.document.DocumentSearchService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.FileNotFoundException; import java.util.Arrays; @@ -96,10 +95,10 @@ public void init() { netObjects = factory.createNetObjects(); publicUser = context.getPublicAccount(); documentCreator = new DocumentCreator( - fileEntityService, + fileObjectService, collectionService, nodeService, - termVectorEntityService); + termVectorService); try { col = documentCreator.uploadDocuments( @@ -175,9 +174,8 @@ public static WebArchive createDeployment() { .addClass(TissueService.class) .addClass(SearchOrchestrator.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(CollectionService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(SearchWebClient.class) .addClass(ExperimentService.class) .addClass(ExpRecordService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java index b83b5a8b5..be529d1fc 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java @@ -21,12 +21,12 @@ * * @author fmauz */ +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.base.DocumentCreator; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.FileService; @@ -40,7 +40,6 @@ import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.search.relevance.RelevanceCalculator; import de.ipb_halle.lbac.service.NodeService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.FileNotFoundException; import java.nio.file.Paths; @@ -87,10 +86,10 @@ public void init() { publicUser = memberService.loadUserById(GlobalAdmissionContext.PUBLIC_ACCOUNT_ID); documentCreator = new DocumentCreator( - fileEntityService, + fileObjectService, collectionService, nodeService, - termVectorEntityService); + termVectorService); try { col = documentCreator.uploadDocuments( @@ -128,7 +127,7 @@ public void test001_loadDocuments_withoutWordRoot() throws FileNotFoundException @Test public void test002_loadDocuments_withOneWordRoot() throws FileNotFoundException, InterruptedException { - RelevanceCalculator calculator = new RelevanceCalculator(Arrays.asList("java")); + RelevanceCalculator calculator = new RelevanceCalculator(new HashSet (Arrays.asList("java", "jav"))); DocumentSearchRequestBuilder builder = new DocumentSearchRequestBuilder(publicUser, 0, 25); builder.setCollectionId(col.getId()); @@ -181,7 +180,7 @@ public void getTagStringForSeachRequestTest() { public static WebArchive createDeployment() { return prepareDeployment("DocumentSearchServiceTest.war") .addClass(DocumentSearchService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(CloudService.class) .addClass(CloudNodeService.class) .addClass(NodeService.class) @@ -189,8 +188,7 @@ public static WebArchive createDeployment() { .addClass(ACListService.class) .addClass(FileService.class) .addClass(MembershipService.class) - .addClass(MemberService.class) - .addClass(TermVectorEntityService.class); + .addClass(MemberService.class); } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java index 430149f69..5876e7645 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentDownloadBeanTest.java @@ -17,26 +17,8 @@ */ package de.ipb_halle.lbac.search.document.download; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Date; -import javax.inject.Inject; - -import org.apache.commons.io.IOUtils; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit5.ArquillianExtension; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.MemberService; @@ -47,7 +29,6 @@ import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.NetObjectImpl; import de.ipb_halle.lbac.search.bean.NetObjectFactory; @@ -57,8 +38,25 @@ import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; -import de.ipb_halle.tx.file.FileObject; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Date; +import javax.inject.Inject; + +import org.apache.commons.io.IOUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; /** * @author flange @@ -74,7 +72,7 @@ public class DocumentDownloadBeanTest extends TestBase { private CollectionService collectionService; @Inject - private FileEntityService fileEntityService; + private FileObjectService fileObjectService; @Inject private DocumentDownloadBean bean; @@ -154,7 +152,7 @@ public void test_actionDownload_local_documentDoesNotExistInDatabase() throws IO @Test public void test_actionDownload_local_fileDoesNotExistInFilesystem() throws IOException { String nonexistingFilename = tmpPath + "/nonexistingFile"; - FileObject fileObject = fileEntityService.save(createFileObject(nonexistingFilename)); + FileObject fileObject = fileObjectService.save(createFileObject(nonexistingFilename)); Document doc = new NetObjectFactory().createDocument("public", localNode, "test.pdf"); doc.setCollection(readableCollection); doc.setId(fileObject.getId()); @@ -169,7 +167,7 @@ public void test_actionDownload_local_fileDoesNotExistInFilesystem() throws IOEx public void test_actionDownload_local_successfulDownload() throws IOException { String content = "Hello World"; String path = createTempFile(content); - FileObject fileObject = fileEntityService.save(createFileObject(path)); + FileObject fileObject = fileObjectService.save(createFileObject(path)); Document doc = new NetObjectFactory().createDocument("public", localNode, "test.pdf"); doc.setCollection(readableCollection); doc.setId(fileObject.getId()); @@ -214,7 +212,7 @@ public void test_actionDownload_remote_successfulDownload() throws IOException { public void test_actionDownload_local_successfulDownload_Document() throws IOException { String content = "Hello World"; String path = createTempFile(content); - FileObject fileObject = fileEntityService.save(createFileObject(path)); + FileObject fileObject = fileObjectService.save(createFileObject(path)); Document doc = new NetObjectFactory().createDocument("public", localNode, "test.pdf"); doc.setCollection(readableCollection); doc.setId(fileObject.getId()); @@ -253,7 +251,7 @@ private String createTempFile(String content) throws IOException { public static WebArchive createDeployment() { WebArchive deployment = prepareDeployment("DocumentDownloadBeanTest.war") .addClass(NodeService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(CollectionService.class) .addClass(ACListService.class) .addClass(MembershipService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java index 73871c5c3..f95ef604f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/download/DocumentWebServiceTest.java @@ -17,8 +17,26 @@ */ package de.ipb_halle.lbac.search.document.download; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.lbac.admission.ACListService; +import de.ipb_halle.lbac.admission.MemberService; +import de.ipb_halle.lbac.admission.MembershipService; +import de.ipb_halle.lbac.admission.User; +import de.ipb_halle.lbac.admission.UserBeanDeployment; +import de.ipb_halle.lbac.base.TestBase; +import de.ipb_halle.lbac.collections.Collection; +import de.ipb_halle.lbac.collections.CollectionService; +import de.ipb_halle.lbac.entity.CloudNode; +import de.ipb_halle.lbac.entity.Node; +import de.ipb_halle.lbac.globals.KeyManager; +import de.ipb_halle.lbac.service.CloudNodeService; +import de.ipb_halle.lbac.service.CloudService; +import de.ipb_halle.lbac.service.NodeService; +import de.ipb_halle.lbac.webclient.LbacWebClient; +import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; + import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -37,26 +55,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - -import de.ipb_halle.lbac.admission.ACListService; -import de.ipb_halle.lbac.admission.MemberService; -import de.ipb_halle.lbac.admission.MembershipService; -import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.admission.UserBeanDeployment; -import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.collections.Collection; -import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.entity.CloudNode; -import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.file.FileEntityService; -import de.ipb_halle.lbac.globals.KeyManager; -import de.ipb_halle.lbac.service.CloudNodeService; -import de.ipb_halle.lbac.service.CloudService; -import de.ipb_halle.lbac.service.NodeService; -import de.ipb_halle.lbac.webclient.LbacWebClient; -import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; -import de.ipb_halle.tx.file.FileObject; -import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /** * @author flange @@ -133,7 +133,7 @@ public void test_downloadDocument_documentDoesNotExistInDatabase() throws Except @Test public void test_downloadDocument_userIsNotPermitted() throws Exception { String nonexistingFilename = tmpPath + "/nonexistingFile"; - FileObject fileObject = fileEntityService.save(createFileObject(nonexistingFilename, nonReadableCollection)); + FileObject fileObject = fileObjectService.save(createFileObject(nonexistingFilename, nonReadableCollection)); DocumentWebRequest request = new DocumentWebRequest(); request.setFileObjectId(fileObject.getId()); signRequest(request, localNode, localRequestingUser); @@ -147,7 +147,7 @@ public void test_downloadDocument_userIsNotPermitted() throws Exception { @Test public void test_downloadDocument_fileDoesNotExistInFilesystem() throws Exception { String nonexistingFilename = tmpPath + "/nonexistingFile"; - FileObject fileObject = fileEntityService.save(createFileObject(nonexistingFilename, readableCollection)); + FileObject fileObject = fileObjectService.save(createFileObject(nonexistingFilename, readableCollection)); DocumentWebRequest request = new DocumentWebRequest(); request.setFileObjectId(fileObject.getId()); signRequest(request, localNode, localRequestingUser); @@ -162,7 +162,7 @@ public void test_downloadDocument_fileDoesNotExistInFilesystem() throws Exceptio public void test_downloadDocument_successfulDownload() throws Exception { String content = "Hello World"; String path = createTempFile(content); - FileObject fileObject = fileEntityService.save(createFileObject(path, readableCollection)); + FileObject fileObject = fileObjectService.save(createFileObject(path, readableCollection)); DocumentWebRequest request = new DocumentWebRequest(); request.setFileObjectId(fileObject.getId()); signRequest(request, localNode, localRequestingUser); @@ -209,7 +209,7 @@ private void signRequest(DocumentWebRequest request, Node node, User user) throw @Deployment public static WebArchive createDeployment() { WebArchive deployment = prepareDeployment("DocumentWebServiceTest.war") - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(CollectionService.class) .addClass(ACListService.class) .addClass(MembershipService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java index 815a72394..242010def 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/relevance/RelevanceCalculatorTest.java @@ -17,12 +17,11 @@ */ package de.ipb_halle.lbac.search.relevance; +import de.ipb_halle.kx.termvector.TermFrequency; +import de.ipb_halle.kx.termvector.TermFrequencyList; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.search.document.StemmedWordGroup; -import de.ipb_halle.tx.file.TermFrequency; -import de.ipb_halle.tx.file.TermFrequencyList; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -47,10 +46,7 @@ public class RelevanceCalculatorTest { */ @Test public void calculateRelevanceFactorsTest() { - instance = new RelevanceCalculator(Arrays.asList("java")); - StemmedWordGroup normalizedTerms = new StemmedWordGroup(); - normalizedTerms.addStemmedWord("java", new HashSet<>(Arrays.asList("java","jav"))); - instance.setSearchTerms(normalizedTerms); + instance = new RelevanceCalculator(new HashSet<>(Arrays.asList("java","jav"))); int totalDocsInFirstIteration = 10; double averageWordsInCollection = 55; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java index 773b73da0..2534a88e3 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.search.wordcloud; +import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.globals.KeyManager; @@ -24,7 +25,6 @@ import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.FileService; @@ -38,7 +38,6 @@ import de.ipb_halle.lbac.collections.CollectionOrchestrator; import de.ipb_halle.lbac.collections.CollectionWebClient; import de.ipb_halle.lbac.service.NodeService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.search.wordcloud.mock.WordCloudWebServiceMock; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -90,10 +89,10 @@ public void before() throws IOException { userBean.setCurrentAccount(publicUser); DocumentCreator documentCreator = new DocumentCreator( - fileEntityService, + fileObjectService, collectionService, nodeService, - termVectorEntityService); + termVectorService); try { Collection col = documentCreator.uploadDocuments( @@ -160,7 +159,7 @@ public static WebArchive createDeployment() { return UserBeanDeployment.add(prepareDeployment("WordCloudBeanTest.war") .addClass(DocumentSearchService.class) .addClass(WordCloudBean.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(NodeService.class) .addClass(CloudService.class) .addClass(CloudNodeService.class) @@ -174,12 +173,9 @@ public static WebArchive createDeployment() { .addClass(CollectionBean.class) .addClass(CollectionWebClient.class) .addClass(CollectionOrchestrator.class) - .addClass(TermVectorEntityService.class) .addClass(KeyManager.class) .addClass(DocumentSearchService.class) .addClass(WordCloudWebService.class) - .addClass(TermVectorEntityService.class) - .addClass(FileEntityService.class) .addClass(WebRequestAuthenticator.class) .addClass(WordCloudWebServiceMock.class)); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebServiceTest.java index a53929b7b..c5e51e003 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudWebServiceTest.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.lbac.search.wordcloud; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.admission.ACList; @@ -26,14 +28,12 @@ import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.collections.CollectionService; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.FileService; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.admission.MembershipService; import de.ipb_halle.lbac.service.NodeService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.search.wordcloud.mock.WordCloudWebServiceMock; import de.ipb_halle.lbac.webservice.service.WebRequestAuthenticator; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -55,7 +55,7 @@ public class WordCloudWebServiceTest extends TestBase { @Inject - private TermVectorEntityService termVectorEntityService; + private TermVectorService termVectorService; @Inject private WordCloudWebService instance; @@ -83,7 +83,7 @@ public static WebArchive createDeployment() { return prepareDeployment("WordCloudWebServiceTest.war") .addClass(DocumentSearchService.class) - .addClass(FileEntityService.class) + .addClass(FileObjectService.class) .addClass(NodeService.class) .addClass(CloudService.class) .addClass(CloudNodeService.class) @@ -93,12 +93,10 @@ public static WebArchive createDeployment() { .addClass(MembershipService.class) .addClass(WordCloudWebClient.class) .addClass(MemberService.class) - .addClass(TermVectorEntityService.class) + .addClass(TermVectorService.class) .addClass(KeyManager.class) .addClass(DocumentSearchService.class) .addClass(WordCloudWebService.class) - .addClass(TermVectorEntityService.class) - .addClass(FileEntityService.class) .addClass(WebRequestAuthenticator.class) .addClass(WordCloudWebServiceMock.class); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java index 7b6dae68c..4dbdf2ea9 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordTermListMergerTest.java @@ -17,8 +17,8 @@ */ package de.ipb_halle.lbac.search.wordcloud; +import de.ipb_halle.kx.termvector.TermFrequency; import de.ipb_halle.lbac.search.document.Document; -import de.ipb_halle.tx.file.TermFrequency; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java index c272527fd..e5d2c017e 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/mock/WordCloudWebServiceMock.java @@ -17,13 +17,13 @@ */ package de.ipb_halle.lbac.search.wordcloud.mock; +import de.ipb_halle.kx.termvector.TermFrequency; +import de.ipb_halle.kx.termvector.TermFrequencyList; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.search.document.Document; import de.ipb_halle.lbac.entity.Node; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.search.wordcloud.WordCloudWebRequest; -import de.ipb_halle.tx.file.TermFrequency; -import de.ipb_halle.tx.file.TermFrequencyList; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.POST; diff --git a/ui/src/test/java/de/ipb_halle/lbac/service/InfoObjectServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/service/InfoObjectServiceTest.java index 0ea8de090..c4d0e4b12 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/service/InfoObjectServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/service/InfoObjectServiceTest.java @@ -21,7 +21,6 @@ import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.MembershipService; import de.ipb_halle.lbac.admission.MemberService; -import de.ipb_halle.lbac.search.termvector.TermVectorEntityService; import de.ipb_halle.lbac.admission.AdmissionSubSystemType; import de.ipb_halle.lbac.admission.MembershipOrchestrator; import de.ipb_halle.lbac.base.TestBase; @@ -36,7 +35,6 @@ import de.ipb_halle.lbac.collections.CollectionOrchestrator; import de.ipb_halle.lbac.collections.CollectionSearchState; import de.ipb_halle.lbac.collections.CollectionWebClient; -import de.ipb_halle.lbac.file.FileEntityService; import de.ipb_halle.lbac.search.document.DocumentSearchService; import de.ipb_halle.lbac.webservice.Updater; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -95,11 +93,9 @@ public static WebArchive createDeployment() { .addClass(Updater.class) .addClass(CollectionSearchState.class) .addClass(ACListService.class) - .addClass(FileEntityService.class) .addClass(FileService.class) .addClass(CollectionBean.class) .addClass(DocumentSearchService.class) - .addClass(TermVectorEntityService.class) .addClass(MembershipOrchestrator.class); } From 0a37b2836fa5cda25b8fece2dec5ccc32de4cafc Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Thu, 21 Sep 2023 16:24:38 +0200 Subject: [PATCH 09/28] Separate Text Extraction from UI (WORK IN PROGRESS) Tests run: 769, Failures: 5, Errors: 15, Skipped: 2 --- .../kx/termvector/TermVectorService.java | 16 +++++++++------- .../java/de/ipb_halle/lbac/base/TestBase.java | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java index 0fb617c01..3015816ab 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java @@ -59,13 +59,15 @@ public class TermVectorService implements Serializable { protected final String SQL_DELETE_ALL_TERMVECTORS = "DELETE from termvectors"; + + private final String SQL_DELETE_UNSTEMMED_WORDS_OF_COLLECTION + = "DELETE FROM unstemmed_words AS us USING files AS f" + + " WHERE us.file_id=f.id AND f.collection_id=:collectionId"; + private final String SQL_DELETE_TERMVECTORS_OF_COLLECTION - = "DELETE from termvectors AS tv ... " - + " WHERE fo.collection_id=:collectionId"; + = "DELETE FROM termvectors AS tv USING files AS f" + + " WHERE tv.file_id=f.id AND f.collection_id=:collectionId"; - private final String SQL_DELETE_ORIGINAL_WORDS_OF_COLLECTION - = "DELETE FROM unstemmed_words AS stem ... "; - + " WHERE fo.collection_id=:collectionId"; @PersistenceContext(name = "de.ipb_halle.lbac") @@ -159,7 +161,7 @@ public void deleteTermVectorsOfCollection(Integer collectionId) { this.em.createNativeQuery(SQL_DELETE_TERMVECTORS_OF_COLLECTION) .setParameter("collectionId", collectionId) .executeUpdate(); - this.em.createNativeQuery(SQL_DELETE_ORIGINAL_WORDS_OF_COLLECTION) + this.em.createNativeQuery(SQL_DELETE_UNSTEMMED_WORDS_OF_COLLECTION) .setParameter("collectionId", collectionId) .executeUpdate(); } @@ -245,8 +247,8 @@ public List loadUnstemmedWordsOfDocument( * Deletes all termvectors and all unstemmed words from database. */ public void deleteTermVectors() { - this.em.createNativeQuery(SQL_DELETE_ALL_UNSTEMMED_WORDS).executeUpdate(); this.em.createNativeQuery(SQL_DELETE_ALL_TERMVECTORS).executeUpdate(); + this.em.createNativeQuery(SQL_DELETE_ALL_UNSTEMMED_WORDS).executeUpdate(); this.em.flush(); } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java index 17d0b53cf..d279bf747 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java @@ -406,7 +406,7 @@ public void resetCollectionsInDb(CollectionService collectionService) { List colls = collectionService.load(null); for (Collection c : colls) { if (!c.getName().equals("public")) { - termVectorService.deleteTermVectorsOfCollection(c); + termVectorService.deleteTermVectorsOfCollection(c.getId()); fileObjectService.deleteCollectionFiles(c.getId()); collectionService.delete(c); } From a0a2d42f423614b37153b82ce329dc92445695ab Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Thu, 21 Sep 2023 17:39:36 +0200 Subject: [PATCH 10/28] minor cleanups --- .../kx/termvector/TermVectorService.java | 1 + .../de/ipb_halle/kx/service/FileAnalyserTest.java | 2 +- .../FilterDefinitionInputStreamFactory.java | 2 +- .../de/ipb_halle/lbac/webservice/Updater.java | 3 --- .../de/ipb_halle/lbac/file/UploadToColTest.java | 8 ++++---- .../document/DocumentSearchServiceTest.java | 2 +- ...NumberExample.docx => ShortNumberExample.docx} | Bin .../exampledocs/~$all_Number_Example2.docx | Bin 162 -> 0 bytes ui/src/test/resources/test-persistence.xml | 4 ++-- ui/web/WEB-INF/persistence.xml | 4 ++-- 10 files changed, 12 insertions(+), 14 deletions(-) rename {ui/src/test/java/de/ipb_halle/lbac/file => kx-web/src/test/java/de/ipb_halle/kx/service}/FilterDefinitionInputStreamFactory.java (96%) rename ui/src/test/resources/exampledocs/{ShotNumberExample.docx => ShortNumberExample.docx} (100%) delete mode 100644 ui/src/test/resources/exampledocs/~$all_Number_Example2.docx diff --git a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java index 3015816ab..dd64ae24f 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/TermVectorService.java @@ -164,6 +164,7 @@ public void deleteTermVectorsOfCollection(Integer collectionId) { this.em.createNativeQuery(SQL_DELETE_UNSTEMMED_WORDS_OF_COLLECTION) .setParameter("collectionId", collectionId) .executeUpdate(); + this.em.flush(); } public void deleteTermVector(FileObject fileObject) { diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java index 3c850f920..eb6c7c697 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java @@ -17,9 +17,9 @@ */ package de.ipb_halle.kx.service; +import de.ipb_halle.kx.service.FilterDefinitionInputStreamFactory; import de.ipb_halle.kx.termvector.StemmedWordOrigin; import de.ipb_halle.kx.termvector.TermVector; -import de.ipb_halle.lbac.file.FilterDefinitionInputStreamFactory; import java.io.FileNotFoundException; import java.util.List; import java.util.Set; diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/FilterDefinitionInputStreamFactory.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FilterDefinitionInputStreamFactory.java similarity index 96% rename from ui/src/test/java/de/ipb_halle/lbac/file/FilterDefinitionInputStreamFactory.java rename to kx-web/src/test/java/de/ipb_halle/kx/service/FilterDefinitionInputStreamFactory.java index bd69b8e76..0701faa31 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/FilterDefinitionInputStreamFactory.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FilterDefinitionInputStreamFactory.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.file; +package de.ipb_halle.kx.service; import java.io.InputStream; diff --git a/ui/src/main/java/de/ipb_halle/lbac/webservice/Updater.java b/ui/src/main/java/de/ipb_halle/lbac/webservice/Updater.java index 61457e499..74065bf8a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/webservice/Updater.java +++ b/ui/src/main/java/de/ipb_halle/lbac/webservice/Updater.java @@ -58,9 +58,6 @@ public class Updater { /** * initalize the update timer. - *

- * xxxxx ToDo: Timer configuration possibly should come from a properties - * file (or be otherwise configurable). */ @PostConstruct public void postConstruct() { diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java index aa81b6c25..60cb26fac 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java @@ -61,7 +61,7 @@ public class UploadToColTest extends TestBase { @Inject private FileObjectService fileObjectService; - protected String examplaDocsRootFolder = "target/test-classes/exampledocs/"; + protected String exampleDocsRootFolder = "target/test-classes/exampledocs/"; protected User publicUser; protected Collection col; @@ -88,7 +88,7 @@ public void test001_fileUploadTest() throws Exception { fileObjectService, publicUser, new AsyncContextMock( - new File(examplaDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), + new File(exampleDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), col.getName()), collectionService, "target/test-classes/collections"); @@ -120,7 +120,7 @@ public void test002_fileUploadTestNoCollectionFound() throws Exception { fileObjectService, publicUser, new AsyncContextMock( - new File(examplaDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), + new File(exampleDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), "test-coll-does-not-exist"), collectionService, "target/test-classes/collections"); @@ -139,7 +139,7 @@ public void test003_fileUploadWithSmallNumbers() throws Exception { fileObjectService, publicUser, new AsyncContextMock( - new File(examplaDocsRootFolder + "ShotNumberExample.docx"), + new File(exampleDocsRootFolder + "ShortNumberExample.docx"), col.getName()), collectionService, "target/test-classes/collections"); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java index be529d1fc..a8bd60b61 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java @@ -69,7 +69,7 @@ public class DocumentSearchServiceTest extends TestBase { protected Collection col; - protected String examplaDocsRootFolder = "target/test-classes/exampledocs/"; + protected String exampleDocsRootFolder = "target/test-classes/exampledocs/"; protected User publicUser; protected AsyncContextMock asynContext; private DocumentCreator documentCreator; diff --git a/ui/src/test/resources/exampledocs/ShotNumberExample.docx b/ui/src/test/resources/exampledocs/ShortNumberExample.docx similarity index 100% rename from ui/src/test/resources/exampledocs/ShotNumberExample.docx rename to ui/src/test/resources/exampledocs/ShortNumberExample.docx diff --git a/ui/src/test/resources/exampledocs/~$all_Number_Example2.docx b/ui/src/test/resources/exampledocs/~$all_Number_Example2.docx deleted file mode 100644 index 2d5707007c57a3cc1c4481964ad9f2e4e6e56c71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmd<_O)Ra_QE*F4%1q2-AQtd2_%b9ilrmH?=rAZSxB=No44FU@W_fz s7=f4rh$TQAG|*o%b2S4mLx(ei6+;X|C_^zrDnk*FjA2M)$c5Sf0HO~QuK)l5 diff --git a/ui/src/test/resources/test-persistence.xml b/ui/src/test/resources/test-persistence.xml index 9f1021c93..041155cac 100644 --- a/ui/src/test/resources/test-persistence.xml +++ b/ui/src/test/resources/test-persistence.xml @@ -92,8 +92,8 @@ de.ipb_halle.lbac.forum.PostingEntity de.ipb_halle.lbac.util.pref.PreferenceEntity de.ipb_halle.lbac.reporting.report.ReportEntity - de.ipb_halle.tx.file.FileObjectEntity - de.ipb_halle.tx.file.TermVectorEntity + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity de.ipb_halle.lbac.search.lang.HorrorEntity diff --git a/ui/web/WEB-INF/persistence.xml b/ui/web/WEB-INF/persistence.xml index b78ee2645..7577194fd 100644 --- a/ui/web/WEB-INF/persistence.xml +++ b/ui/web/WEB-INF/persistence.xml @@ -97,8 +97,8 @@ de.ipb_halle.lbac.items.entity.ItemEntity de.ipb_halle.lbac.items.entity.ItemHistoryEntity de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity - de.ipb_halle.tx.file.FileObjectEntity - de.ipb_halle.tx.file.TermVectorEntity + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity true From f0317307900945376a111ac61fc662d3c2677696 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Thu, 28 Sep 2023 08:18:32 +0200 Subject: [PATCH 11/28] Text Analysis WebService (WORK IN PROGRESS) --- .../kx/service/TextWebRequestType.java | 32 ++++++++ .../ipb_halle/kx/service/TextWebStatus.java | 31 +++++++ .../de/ipb_halle/kx/service/FileAnalyser.java | 78 +++++++++--------- .../de/ipb_halle/kx/service/JobTracker.java | 48 +++++++++++ .../ipb_halle/kx/service/TextWebService.java | 81 ++++++++++++++++++- 5 files changed, 231 insertions(+), 39 deletions(-) create mode 100644 kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java create mode 100644 kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java create mode 100644 kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java diff --git a/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java new file mode 100644 index 000000000..48abc4b5c --- /dev/null +++ b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java @@ -0,0 +1,32 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +/** + * Provisional result type class for TextWebService (module kx-web) until + * job API gets refactored. + * + * @author fbroda + */ +public enum TextWebRequestType { + + public final static String PARAMETER="type"; + + SUBMIT, + QUERY; +} diff --git a/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java new file mode 100644 index 000000000..f79686d3a --- /dev/null +++ b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java @@ -0,0 +1,31 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +/** + * Provisional class for TextWebService until the job API gets + * refactored. + * + * @author fbroda + */ +public enum TextWebStatus { + + BUSY, + DONE, + ERROR; +} diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java index f45215036..86ff0d971 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java @@ -38,24 +38,29 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; + /** * * @author fmauz */ -public class FileAnalyser { +public class FileAnalyser implements Runnable { - protected ParseTool parseTool = new ParseTool(); - protected InputStream filterDefinition; + public final String FILTER_DEFINITION = "fileParserFilterDefinition.json"; - private FileObjectService fileObjectService; + protected ParseTool parseTool; + protected InputStream filterDefinition; + private FileObject fileObject; + private TextWebStatus status; private Logger logger = LogManager.getLogger(this.getClass()); public FileAnalyser(InputStream filterDefinition) { - this.filterDefinition = filterDefinition; + this.parseTool = new ParseTool(); + this.filterDefinition = this.getClass().getResourceAsStream(FILTER_DEFINITION); + this.status = TextWebStatus.BUSY; } - private String getLanguage() { + public String getLanguage() { @SuppressWarnings("unchecked") SortedSet languages = (SortedSet) this.parseTool .getFilterData() @@ -89,7 +94,25 @@ protected void analyseFile(InputStream file){ parseTool.parse(); } - protected List getWordOrigins() { + public FileObject getFileObject() { + return fileObject; + } + + public TextWebStatus getStatus() { + return status; + } + + public List getTermVector() { + List termVectors = new ArrayList<>(); + @SuppressWarnings("unchecked") + Map termvectorMap = (Map) parseTool.getFilterData().getValue(TermVectorFilter.TERM_VECTOR); + for (String tv : termvectorMap.keySet()) { + termVectors.add(new TermVector(tv, fileObject.getId(), termvectorMap.get(tv))); + } + return termVectors; + } + + public List getWordOrigins() { List wordOrigins = new ArrayList<>(); @SuppressWarnings("unchecked") Map> map = (Map) parseTool.getFilterData().getValue(TermVectorFilter.STEM_DICT); @@ -101,43 +124,24 @@ protected List getWordOrigins() { return wordOrigins; } - protected List getTermVector(Integer fileId) { - List termVectors = new ArrayList<>(); - @SuppressWarnings("unchecked") - Map termvectorMap = (Map) parseTool.getFilterData().getValue(TermVectorFilter.TERM_VECTOR); - for (String tv : termvectorMap.keySet()) { - termVectors.add(new TermVector(tv, fileId, termvectorMap.get(tv))); - } - return termVectors; - } - - public void processFile(Integer fileId) { + public void run() { try { - FileObject fileObj = fileObjectService.loadFileObjectById(fileId); - FileInputStream is = new FileInputStream(fileObj.getFileLocation()); + FileInputStream is = new FileInputStream(fileObject.getFileLocation()); analyseFile(is); - saveResults(fileObj); + status = TextWebStatus.DONE; } catch (Exception e) { - + logger.warn((Throwable e) + status = TextWebStatus.ERROR; } } - private void saveResults(FileObject fileObj) { - saveTermVector(); - saveWordOrigins(); - updateLanguage(fileObj); - } - - private void saveTermVector() { - - } - - private void saveWordOrigins() { - + public FileAnalyser setFileObject(FileObject f) { + fileObject = f; + return this; } - private void updateLanguage(FileObject fileObj) { - fileObj.setDocumentLanguage(getLanguage()); - fileObjectService.save(fileObj); + public FileAnalyser setFilterDefinition(InputStream def) { + filterDefinition = def; + return this; } } diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java b/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java new file mode 100644 index 000000000..0da78bf6c --- /dev/null +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java @@ -0,0 +1,48 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +import de.ipb_halle.kx.service.TextWebStatus; +import java.util.HashMap; +import java.util.Map; +import javax.ejb.Singleton; +import javax.ejb.Startup; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +/** + * Provisional class for job tracking until Job API gets refactored. + */ +@Singleton +@Startup +public class JobTracker { + + private Map jobMap = new HashMap<> (); + + public FileAnalyser getJob(Integer id) { + return jobMap.get(id); + } + + public void pubJob(Integer id, FileAnalyser job) { + jobMap.put(id, job); + } + + public void remove(Integer id) { + jobMap.remove(id); + } +} diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java index 348ed65fa..2a9cb8944 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java @@ -37,15 +37,92 @@ public class TextWebService extends HttpServlet { private final Logger logger = LogManager.getLogger(TextWebService.class); + @Resource(name = "kxExecutorService") + private ManagedExecutorService managedExecutorService; + + @Inject + private JobTracker jobTracker; + + @Inject + private FileObjectService fileObjectService; + + @Inject + private TermVectorService termVectorService; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { logger.info("doGet(): request received."); try { - final String fileId = req.getParameter("fileId"); + final TextWebRequestType requestType = TextWebRequestType.valueOf(req.getParameter(TextWebRequestType.PARAMETER)); + final Integer fileId = Integer.parseInt(req.getParameter("fileId")); final PrintWriter out = resp.getWriter(); - out.write("Submitted: " + fileId); + out.write(processRequest.toString()); } catch (IOException e) { logger.error((Throwable) e); } } + + private TextWebStatus processRequest(Integer fileId, TextWebRequestType requestType) { + try { + if (requestType == SUBMIT) { + return processSubmitRequest(fileId); + } else { + return processQueryRequest(fileId); + } + } catch (Exception e) { + logger.warn((Throwable) e); + } + return TextWebStatus.ERROR; + } + + private TextWebStatus processSubmitRequest(Integer fileId) { + FileObject fileObj = fileObjectService.loadFileObjectById(fileId); + + FileAnalyser analyser = new FileAnalyser() + .setFileObject(fileObj); + jobTracker.putJob(fileId, analyser); + managedExecutorService.submit(analyser); + return analyser.getStatus(); + } + + private TextWebStatus processQueryRequest(Integer fileId) { + FileAnalyser analyser = jobTracker.getJob(fileId); + if (analyser == null) { + return TextWebStatus.ERROR; + } + TextWebStatus status = analyser.getStatus(); + + if (status == TextWebStatus.BUSY) { + return status; + } + + if (status == TextWebStatus.DONE) { + saveResults(analyser); + } + + // ERROR or DONE + jobTracker.remove(fileId); + return status; + } + + private void saveResults(FileAnalyser analyser) { + saveLanguage(analyser); + termVectorService.saveUnstemmedWordsOfDocument( + analyser.getWordOrigins(), + analyser.getFileObject().getId()); + termVectorService.saveTermVectors(analyser.getTermVector()); + } + + private void saveLanguage(FileAnalyser analyser) { + FileObject fileObj = fileObjectService.loadFileObjectById( + analyser.getFileObject().getId()); + fileObj.setLanguage( + analyser.getLanguage()); + fileObjectService.save(fileObj); + } + + // for testing + protected void setExecutorService(ManagedExecutorService mes) { + managedExecutorService = mes; + } } From fab3a6add89081bf9884073c14e258e81b9ac3b1 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Fri, 29 Sep 2023 16:41:09 +0200 Subject: [PATCH 12/28] Separate Text Extraction from UI (WORK IN PROGRESS) - separated some mock classes into the crimsy-test module - repaired some unit tests. --- crimsy-test/pom.xml | 164 +++ .../ipb_halle/test}/EntityManagerService.java | 2 +- .../test}/ManagedExecutorServiceMock.java | 16 +- .../PostgresqlContainerExtension.java | 33 +- kx-api/pom.xml | 8 + .../kx/service/TextWebRequestType.java | 4 +- .../ipb_halle/kx/service/TextWebStatus.java | 5 +- .../kx/file/FileObjectServiceTest.java | 3 +- .../kx/termvector/TermVectorServiceTest.java | 3 +- .../resources/PostgresqlContainerSchemaFiles | 5 + kx-web/pom.xml | 16 +- .../de/ipb_halle/kx/service/FileAnalyser.java | 13 +- .../ipb_halle/kx/service/IFileAnalyser.java | 42 + .../kx/service/IFileAnalyserFactory.java | 28 + .../de/ipb_halle/kx/service/JobTracker.java | 11 +- .../ipb_halle/kx/service/TextWebService.java | 68 +- .../service}/fileParserFilterDefinition.json | 0 .../kx/service/FileAnalyserFactoryMock.java | 30 + .../kx/service/FileAnalyserMock.java | 96 ++ .../kx/service/FileAnalyserTest.java | 42 +- .../kx/service/TextWebServiceTest.java | 78 +- .../resources/PostgresqlContainerSchemaFiles | 5 + .../test/resources/exampledocs/Document1.pdf | Bin 0 -> 180438 bytes kx-web/src/test/resources/schema/00001.sql | 969 ++++++++++++++++++ .../src/test/resources/test-persistence.xml | 55 + pom.xml | 1 + .../document/DocumentSearchServiceTest.java | 1 - .../resources/PostgresqlContainerSchemaFiles | 7 + 28 files changed, 1620 insertions(+), 85 deletions(-) create mode 100644 crimsy-test/pom.xml rename {kx-api/src/test/java/de/ipb_halle/testcontainers => crimsy-test/src/main/java/de/ipb_halle/test}/EntityManagerService.java (96%) rename {ui/src/test/java/de/ipb_halle/lbac/reporting/job => crimsy-test/src/main/java/de/ipb_halle/test}/ManagedExecutorServiceMock.java (89%) rename {kx-api/src/test => crimsy-test/src/main}/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java (75%) create mode 100644 kx-api/src/test/resources/PostgresqlContainerSchemaFiles create mode 100644 kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyser.java create mode 100644 kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyserFactory.java rename {ui/src/main/resources/de/ipb_halle/lbac/file => kx-web/src/main/resources/de/ipb_halle/kx/service}/fileParserFilterDefinition.json (100%) create mode 100644 kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserFactoryMock.java create mode 100644 kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserMock.java create mode 100644 kx-web/src/test/resources/PostgresqlContainerSchemaFiles create mode 100644 kx-web/src/test/resources/exampledocs/Document1.pdf create mode 100644 kx-web/src/test/resources/schema/00001.sql create mode 100644 kx-web/src/test/resources/test-persistence.xml create mode 100644 ui/src/test/resources/PostgresqlContainerSchemaFiles diff --git a/crimsy-test/pom.xml b/crimsy-test/pom.xml new file mode 100644 index 000000000..15eb4175e --- /dev/null +++ b/crimsy-test/pom.xml @@ -0,0 +1,164 @@ + + + 4.0.0 + + de.ipb-halle + crimsy-test + 1.0.0 + jar + + CRIMSY-TEST + http://github.com/ipb-halle/CRIMSy + + + UTF-8 + + + 5.3.26.Final + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + + + + + + + make-assembly + package + + single + + + + + + + + + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + org.apache.tomee + javaee-api + 7.0-2 + provided + + + + + + org.slf4j + slf4j-api + 1.7.30 + provided + + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + provided + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + + + org.testcontainers + postgresql + 1.16.3 + + + + diff --git a/kx-api/src/test/java/de/ipb_halle/testcontainers/EntityManagerService.java b/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java similarity index 96% rename from kx-api/src/test/java/de/ipb_halle/testcontainers/EntityManagerService.java rename to crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java index a895e9562..268d54f56 100644 --- a/kx-api/src/test/java/de/ipb_halle/testcontainers/EntityManagerService.java +++ b/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.testcontainers; +package de.ipb_halle.test; import javax.ejb.Stateless; import javax.persistence.EntityManager; diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ManagedExecutorServiceMock.java b/crimsy-test/src/main/java/de/ipb_halle/test/ManagedExecutorServiceMock.java similarity index 89% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/job/ManagedExecutorServiceMock.java rename to crimsy-test/src/main/java/de/ipb_halle/test/ManagedExecutorServiceMock.java index b522d0d93..6e832ba69 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ManagedExecutorServiceMock.java +++ b/crimsy-test/src/main/java/de/ipb_halle/test/ManagedExecutorServiceMock.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.job; +package de.ipb_halle.test; import java.util.ArrayList; import java.util.Collection; @@ -30,17 +30,23 @@ import javax.enterprise.concurrent.ManagedExecutorService; /** - * Mock implementation of ManagedExecutorService that can hold two 2 submitted + * Mock implementation of ManagedExecutorService that can hold maxTasks submitted * tasks. * * @author flange */ public class ManagedExecutorServiceMock implements ManagedExecutorService { + + private int maxTasks; private List submittedTasks = new ArrayList<>(); + public ManagedExecutorServiceMock(int maxTasks) { + this.maxTasks = maxTasks; + } + @Override public Future submit(Runnable task) { - if (submittedTasks.size() < 2) { + if (submittedTasks.size() < maxTasks) { submittedTasks.add(task); } else { throw new RejectedExecutionException(); @@ -115,4 +121,4 @@ public T invokeAny(Collection> tasks, long timeout, Ti @Override public void execute(Runnable command) { } -} \ No newline at end of file +} diff --git a/kx-api/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java b/crimsy-test/src/main/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java similarity index 75% rename from kx-api/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java rename to crimsy-test/src/main/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java index d940c26f6..4a11b7bb8 100644 --- a/kx-api/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java +++ b/crimsy-test/src/main/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java @@ -17,11 +17,16 @@ */ package de.ipb_halle.testcontainers; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.testcontainers.containers.Container.ExecResult; @@ -43,13 +48,13 @@ * @author flange */ public class PostgresqlContainerExtension implements BeforeAllCallback { - private static final String[] SCHEMA_FILES = { "00001.sql" }; + private static final String SCHEMA_FILE_RESOURCE = "/PostgresqlContainerSchemaFiles"; private static final String IMAGE_NAME = "ipbhalle/crimsydb:bingo_pg12"; private static final AtomicBoolean FIRST_RUN = new AtomicBoolean(true); private PostgreSQLContainer container; - private Logger logger = LogManager.getLogger(this.getClass().getName()); + private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void beforeAll(ExtensionContext context) throws Exception { @@ -67,12 +72,30 @@ private void startContainer() throws Exception { logger.info("Starting Postgresql container with image " + customPostgresImage.toString()); container.start(); - for (String schemaFile : SCHEMA_FILES) { + for (String schemaFile : getSchemaFiles()) { copySchema("schema/" + schemaFile); applySchema(schemaFile); } } + private List getSchemaFiles() { + List schemaFileNames = new ArrayList<> (); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader( + this.getClass().getResourceAsStream(SCHEMA_FILE_RESOURCE)))) { + String line = null; + while ((line = reader.readLine()) != null) { + if (! line.startsWith("#")) { + schemaFileNames.add(line /* .strip() */ ); + } + } + } catch (Exception e) { + logger.error("Error configuring schema files for PostgresqlContainerExtension", (Throwable) e); + } + + return schemaFileNames; + } + private void copySchema(String filename) { logger.info("Copy schema file " + filename + " into container path /"); container.copyFileToContainer(MountableFile.forClasspathResource(filename), "/"); diff --git a/kx-api/pom.xml b/kx-api/pom.xml index e35eff4e0..fa20c8e24 100644 --- a/kx-api/pom.xml +++ b/kx-api/pom.xml @@ -114,6 +114,14 @@ 1.0.0 + + + de.ipb-halle + crimsy-test + 1.0.0 + test + + junit diff --git a/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java index 48abc4b5c..5a0c4e764 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebRequestType.java @@ -25,8 +25,8 @@ */ public enum TextWebRequestType { - public final static String PARAMETER="type"; - SUBMIT, QUERY; + + public final static String PARAMETER="type"; } diff --git a/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java index f79686d3a..6fd3bb5c3 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/service/TextWebStatus.java @@ -27,5 +27,8 @@ public enum TextWebStatus { BUSY, DONE, - ERROR; + PARAMETER_ERROR, + PROCESSING_ERROR, + NO_INPUT_ERROR, + NO_SUCH_JOB_ERROR; } diff --git a/kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java b/kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java index 8c4888e2f..d9d0e79e4 100644 --- a/kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java +++ b/kx-api/src/test/java/de/ipb_halle/kx/file/FileObjectServiceTest.java @@ -6,7 +6,7 @@ package de.ipb_halle.kx.file; import de.ipb_halle.kx.file.FileObject; -import de.ipb_halle.testcontainers.EntityManagerService; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -54,6 +54,7 @@ public static WebArchive createDeployment() { WebArchive archive = ShrinkWrap.create(WebArchive.class, "FileObjectServiceTest.war") .addClass(FileObjectService.class) .addClass(EntityManagerService.class) + .addAsResource("PostgresqlContainerSchemaFiles") .addAsWebInfResource("test-persistence.xml", "persistence.xml") .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); return archive; diff --git a/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java b/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java index 7d4d43157..568c46a29 100644 --- a/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java +++ b/kx-api/src/test/java/de/ipb_halle/kx/termvector/TermVectorServiceTest.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.kx.termvector; -import de.ipb_halle.testcontainers.EntityManagerService; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.kx.file.FileObject; @@ -75,6 +75,7 @@ public static WebArchive createDeployment() { .addClass(TermVectorService.class) .addClass(TermVector.class) .addClass(EntityManagerService.class) + .addAsResource("PostgresqlContainerSchemaFiles") .addAsWebInfResource("test-persistence.xml", "persistence.xml") .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); return archive; diff --git a/kx-api/src/test/resources/PostgresqlContainerSchemaFiles b/kx-api/src/test/resources/PostgresqlContainerSchemaFiles new file mode 100644 index 000000000..8fc81a1ec --- /dev/null +++ b/kx-api/src/test/resources/PostgresqlContainerSchemaFiles @@ -0,0 +1,5 @@ +# +# Configuration of PostgresqlContainerExtension: +# schema files for testing +# +00001.sql diff --git a/kx-web/pom.xml b/kx-web/pom.xml index 248227057..055f954f7 100644 --- a/kx-web/pom.xml +++ b/kx-web/pom.xml @@ -168,13 +168,6 @@ - - - de.ipb-halle - kx-api - 1.0.0 - - commons-cli @@ -203,6 +196,7 @@ 1.5 + de.ipb-halle kx-api @@ -296,6 +290,14 @@ --> + + + de.ipb-halle + crimsy-test + 1.0.0 + + + org.junit.jupiter diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java index 86ff0d971..4640ae86f 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java @@ -18,7 +18,6 @@ package de.ipb_halle.kx.service; import de.ipb_halle.kx.file.FileObject; -import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.kx.termvector.StemmedWordOrigin; import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.tx.text.LanguageDetectorFilter; @@ -43,7 +42,7 @@ * * @author fmauz */ -public class FileAnalyser implements Runnable { +public class FileAnalyser implements IFileAnalyser, Runnable { public final String FILTER_DEFINITION = "fileParserFilterDefinition.json"; @@ -54,7 +53,7 @@ public class FileAnalyser implements Runnable { private Logger logger = LogManager.getLogger(this.getClass()); - public FileAnalyser(InputStream filterDefinition) { + public FileAnalyser() { this.parseTool = new ParseTool(); this.filterDefinition = this.getClass().getResourceAsStream(FILTER_DEFINITION); this.status = TextWebStatus.BUSY; @@ -130,17 +129,17 @@ public void run() { analyseFile(is); status = TextWebStatus.DONE; } catch (Exception e) { - logger.warn((Throwable e) - status = TextWebStatus.ERROR; + logger.warn((Throwable) e); + status = TextWebStatus.PROCESSING_ERROR; } } - public FileAnalyser setFileObject(FileObject f) { + public IFileAnalyser setFileObject(FileObject f) { fileObject = f; return this; } - public FileAnalyser setFilterDefinition(InputStream def) { + public IFileAnalyser setFilterDefinition(InputStream def) { filterDefinition = def; return this; } diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyser.java b/kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyser.java new file mode 100644 index 000000000..61552090f --- /dev/null +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyser.java @@ -0,0 +1,42 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; +import java.io.InputStream; +import java.util.List; + + +/** + * Interface of FileAnalyser to enabel Mocking for test cases. + * + * @author fbroda + */ +public interface IFileAnalyser extends Runnable { + + public String getLanguage(); + public FileObject getFileObject(); + public TextWebStatus getStatus(); + public List getTermVector(); + public List getWordOrigins(); + public void run(); + public IFileAnalyser setFileObject(FileObject f); + public IFileAnalyser setFilterDefinition(InputStream def); +} diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyserFactory.java b/kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyserFactory.java new file mode 100644 index 000000000..91f56af05 --- /dev/null +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/IFileAnalyserFactory.java @@ -0,0 +1,28 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +/** + * Factory interface for FileAnalyser to enable mocking for test cases. + * + * @author fbroda + */ +public interface IFileAnalyserFactory { + + public IFileAnalyser buildFileAnalyser(); +} diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java b/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java index 0da78bf6c..a9dff2cf2 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/JobTracker.java @@ -18,6 +18,7 @@ package de.ipb_halle.kx.service; import de.ipb_halle.kx.service.TextWebStatus; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; import javax.ejb.Singleton; @@ -30,15 +31,17 @@ */ @Singleton @Startup -public class JobTracker { +public class JobTracker implements Serializable { - private Map jobMap = new HashMap<> (); + private final static long serialVersionUID = 1L; - public FileAnalyser getJob(Integer id) { + private Map jobMap = new HashMap<> (); + + public IFileAnalyser getJob(Integer id) { return jobMap.get(id); } - public void pubJob(Integer id, FileAnalyser job) { + public void putJob(Integer id, IFileAnalyser job) { jobMap.put(id, job); } diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java index 2a9cb8944..648c07906 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java @@ -17,10 +17,14 @@ */ package de.ipb_halle.kx.service; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.TermVectorService; import java.io.IOException; import java.io.PrintWriter; +import javax.enterprise.concurrent.ManagedExecutorService; +import javax.annotation.Resource; import javax.inject.Inject; - import javax.servlet.AsyncContext; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -35,10 +39,8 @@ public class TextWebService extends HttpServlet { private final static long serialVersionUID = 1L; - private final Logger logger = LogManager.getLogger(TextWebService.class); - @Resource(name = "kxExecutorService") - private ManagedExecutorService managedExecutorService; + private ManagedExecutorService executorService; @Inject private JobTracker jobTracker; @@ -49,22 +51,34 @@ public class TextWebService extends HttpServlet { @Inject private TermVectorService termVectorService; + + static class FileAnalyserFactory implements IFileAnalyserFactory { + public IFileAnalyser buildFileAnalyser() { + return new FileAnalyser(); + } + } + + private static IFileAnalyserFactory fileAnalyserFactory = new FileAnalyserFactory(); + private final Logger logger = LogManager.getLogger(TextWebService.class); + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { logger.info("doGet(): request received."); try { - final TextWebRequestType requestType = TextWebRequestType.valueOf(req.getParameter(TextWebRequestType.PARAMETER)); - final Integer fileId = Integer.parseInt(req.getParameter("fileId")); final PrintWriter out = resp.getWriter(); - out.write(processRequest.toString()); + out.write( + processRequest(req) + .toString()); } catch (IOException e) { logger.error((Throwable) e); } } - private TextWebStatus processRequest(Integer fileId, TextWebRequestType requestType) { + private TextWebStatus processRequest(HttpServletRequest req) { try { - if (requestType == SUBMIT) { + final TextWebRequestType requestType = TextWebRequestType.valueOf(req.getParameter(TextWebRequestType.PARAMETER)); + final Integer fileId = Integer.parseInt(req.getParameter("fileId")); + if (requestType == TextWebRequestType.SUBMIT) { return processSubmitRequest(fileId); } else { return processQueryRequest(fileId); @@ -72,23 +86,27 @@ private TextWebStatus processRequest(Integer fileId, TextWebRequestType requestT } catch (Exception e) { logger.warn((Throwable) e); } - return TextWebStatus.ERROR; + return TextWebStatus.PARAMETER_ERROR; } private TextWebStatus processSubmitRequest(Integer fileId) { FileObject fileObj = fileObjectService.loadFileObjectById(fileId); - - FileAnalyser analyser = new FileAnalyser() - .setFileObject(fileObj); - jobTracker.putJob(fileId, analyser); - managedExecutorService.submit(analyser); - return analyser.getStatus(); + if (fileObj != null) { + IFileAnalyser analyser = fileAnalyserFactory.buildFileAnalyser() + .setFileObject(fileObj); + jobTracker.putJob(fileId, analyser); + executorService.submit(analyser); + return analyser.getStatus(); + } else { + logger.info("Could not obtain FileObject for fileId=" + fileId.toString()); + } + return TextWebStatus.NO_INPUT_ERROR; } private TextWebStatus processQueryRequest(Integer fileId) { - FileAnalyser analyser = jobTracker.getJob(fileId); + IFileAnalyser analyser = jobTracker.getJob(fileId); if (analyser == null) { - return TextWebStatus.ERROR; + return TextWebStatus.NO_SUCH_JOB_ERROR; } TextWebStatus status = analyser.getStatus(); @@ -105,7 +123,7 @@ private TextWebStatus processQueryRequest(Integer fileId) { return status; } - private void saveResults(FileAnalyser analyser) { + private void saveResults(IFileAnalyser analyser) { saveLanguage(analyser); termVectorService.saveUnstemmedWordsOfDocument( analyser.getWordOrigins(), @@ -113,16 +131,20 @@ private void saveResults(FileAnalyser analyser) { termVectorService.saveTermVectors(analyser.getTermVector()); } - private void saveLanguage(FileAnalyser analyser) { + private void saveLanguage(IFileAnalyser analyser) { FileObject fileObj = fileObjectService.loadFileObjectById( analyser.getFileObject().getId()); - fileObj.setLanguage( + fileObj.setDocumentLanguage( analyser.getLanguage()); fileObjectService.save(fileObj); } // for testing - protected void setExecutorService(ManagedExecutorService mes) { - managedExecutorService = mes; + protected void setExecutorService(ManagedExecutorService es) { + executorService = es; + } + + protected void setFileAnalyserFactory(IFileAnalyserFactory factory) { + fileAnalyserFactory = factory; } } diff --git a/ui/src/main/resources/de/ipb_halle/lbac/file/fileParserFilterDefinition.json b/kx-web/src/main/resources/de/ipb_halle/kx/service/fileParserFilterDefinition.json similarity index 100% rename from ui/src/main/resources/de/ipb_halle/lbac/file/fileParserFilterDefinition.json rename to kx-web/src/main/resources/de/ipb_halle/kx/service/fileParserFilterDefinition.json diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserFactoryMock.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserFactoryMock.java new file mode 100644 index 000000000..14a31e422 --- /dev/null +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserFactoryMock.java @@ -0,0 +1,30 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +/** + * Factory for FileAnalyserMock objects + * + * @author fbroda + */ +public class FileAnalyserFactoryMock implements IFileAnalyserFactory { + + public IFileAnalyser buildFileAnalyser() { + return new FileAnalyserMock(); + } +} diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserMock.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserMock.java new file mode 100644 index 000000000..52c26ba88 --- /dev/null +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserMock.java @@ -0,0 +1,96 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.service.TextWebStatus; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; +import java.io.InputStream; +import java.util.List; + +/** + * Interface of FileAnalyser to enabel Mocking for test cases. + * + * @author fbroda + */ +public class FileAnalyserMock implements IFileAnalyser { + + public FileObject fileObject; + public String language; + public List termvectorList; + public List originalWordList; + public TextWebStatus status; + + public FileAnalyserMock() { + status = TextWebStatus.BUSY; + } + + public String getLanguage() { + return language; + } + + public FileObject getFileObject() { + return fileObject; + } + + public TextWebStatus getStatus() { + return status; + } + + public List getTermVector() { + return termvectorList; + } + + public List getWordOrigins() { + return originalWordList; + } + + public void run() { + } + + public IFileAnalyser setFileObject(FileObject f) { + fileObject = f; + return this; + } + + public IFileAnalyser setFilterDefinition(InputStream def) { + return this; + } + + // set status for test purposes + public void setLanguage(String l) { + language = l; + } + + // set status for test purposes + public void setStatus(TextWebStatus s) { + status = s; + } + + // set status for test purposes + public void setTermVectors(List tvl) { + termvectorList = tvl; + } + + // set status for test purposes + public void setWordOrigins(List wol) { + originalWordList = wol; + } +} + diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java index eb6c7c697..39bbe29af 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.kx.service; +import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.kx.service.FilterDefinitionInputStreamFactory; import de.ipb_halle.kx.termvector.StemmedWordOrigin; import de.ipb_halle.kx.termvector.TermVector; @@ -34,11 +35,24 @@ public class FileAnalyserTest { protected String examplaDocsRootFolder = "target/test-classes/exampledocs/"; + public FileObject createMockFile(Integer id, String path) { + FileObject fileObject = new FileObject(); + fileObject.setId(id); + fileObject.setFileLocation(path); + return fileObject; + } + @Test public void test001_analyseEnglishPdf() throws FileNotFoundException, Exception { - FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); - analyser.analyseFile(examplaDocsRootFolder + "Document1.pdf", 1); - List tvs = analyser.getTermVector(); + FileAnalyser analyser = new FileAnalyser(); + analyser.setFileObject(createMockFile(1, examplaDocsRootFolder + "Document1.pdf")); + analyser.run(); + List tvs = null; + try { + tvs = analyser.getTermVector(); + } catch (Exception e) { + e.printStackTrace(); + } Assert.assertEquals(2, tvs.size()); for (TermVector tv : tvs) { if (tv.getWordRoot().equals("java")) { @@ -56,23 +70,22 @@ public void test001_analyseEnglishPdf() throws FileNotFoundException, Exception Assert.assertEquals(2, analyser.getWordOrigins().size()); for (StemmedWordOrigin swo : analyser.getWordOrigins()) { - if (swo.getStemmedWord().equals("java")) { - Assert.assertEquals(1, swo.getOriginalWord().size()); - Assert.assertEquals("java", swo.getOriginalWord().iterator().next()); - continue; - } - if (swo.getStemmedWord().equals("failure")) { - Assert.assertEquals(1, swo.getOriginalWord().size()); - Assert.assertEquals("failure", swo.getOriginalWord().iterator().next()); - continue; + switch (swo.getStemmedWord()) { + case "java" : + Assert.assertEquals("java", swo.getOriginalWord()); + break; + case "failure" : + Assert.assertEquals("failure", swo.getOriginalWord()); + break; + default: + throw new Exception("Unexpected stemmed word found:" + swo.getStemmedWord()); } - throw new Exception("Unexpected stemmed word found:" + swo.getStemmedWord()); } Assert.assertEquals("undefined", analyser.getLanguage()); } - +/* @Test public void test002_analyseGermanXls() throws FileNotFoundException { FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); @@ -123,4 +136,5 @@ public void test006_analyseRealWorldText() throws FileNotFoundException, Excepti FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); analyser.analyseFile(examplaDocsRootFolder + "ShortRealText.docx", 1); } +*/ } diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java index 0e7c2ec14..b9c023806 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java @@ -17,12 +17,19 @@ */ package de.ipb_halle.kx.service; +import de.ipb_halle.test.ManagedExecutorServiceMock; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import de.ipb_halle.kx.file.FileObject; +import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; +import de.ipb_halle.kx.termvector.TermVectorService; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.net.URL; +import java.util.ArrayList; import javax.inject.Inject; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -37,12 +44,12 @@ import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - /** * * @author fblocal @@ -52,26 +59,40 @@ @ExtendWith(ArquillianExtension.class) public class TextWebServiceTest { +// @Resource(name = "DefaultManagedExecutorService") +// private ManagedExecutorService executor; + @ArquillianResource URL baseURL; -// @Inject -// private TextWebService textWebService; + @Inject + private TextWebService textWebService; + + @Inject + private FileObjectService fileObjectService; + + @Inject + private JobTracker jobTracker; @Deployment public static WebArchive createDeployment() { System.setProperty("log4j.configurationFile", "log4j2-test.xml"); WebArchive archive = ShrinkWrap.create(WebArchive.class, "TextWebServiceTest.war") + .addClass(FileObjectService.class) + .addClass(JobTracker.class) + .addClass(TermVectorService.class) .addClass(TextWebService.class) + .addAsResource("PostgresqlContainerSchemaFiles") .addAsWebInfResource("test-persistence.xml", "persistence.xml") .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); return archive; } - // @BeforeEach + @BeforeEach public void init() { - + textWebService.setFileAnalyserFactory(new FileAnalyserFactoryMock()); + textWebService.setExecutorService(new ManagedExecutorServiceMock(2)); } private String streamToString(InputStream inputStream) { @@ -88,19 +109,50 @@ private String streamToString(InputStream inputStream) { return ""; } - @Test - @RunAsClient - public void test001_TextWebService() throws IOException { + private FileObject createFileObject(String location) { + FileObject fo = new FileObject(); + fo.setFileLocation(location); + fo.setName("dummy"); + return fileObjectService.save(fo); + } + + private String doRequest(Integer fileId, TextWebRequestType type) throws IOException { // port number must match the arquillian setting HttpUriRequest request = new HttpGet( - new URL(baseURL, "process?fileId=123").toExternalForm()); + new URL( + baseURL, + String.format("process?fileId=%d&type=%s", fileId, type.toString()) + ).toExternalForm()); HttpResponse response = HttpClientBuilder.create().build().execute(request); - assertEquals(HttpStatus.OK_200, response.getStatusLine().getStatusCode()); + assertEquals(HttpStatus.OK_200, response.getStatusLine().getStatusCode(), "match HTTP status code"); - final String result = streamToString(response.getEntity().getContent()); - System.out.println(result); - assertEquals("Submitted: 123", result); + return streamToString(response.getEntity().getContent()); } + @Test + @RunAsClient + public void test001_TextWebService() throws IOException { + FileObject fo = createFileObject("some_invalid_path"); + + String result = doRequest(-1, TextWebRequestType.SUBMIT); + assertEquals(TextWebStatus.NO_INPUT_ERROR.toString(), result, "error on invalid fileId"); + + result = doRequest(fo.getId(), TextWebRequestType.QUERY); + assertEquals(TextWebStatus.NO_SUCH_JOB_ERROR.toString(), result, "error on non-existent job"); + + result = doRequest(fo.getId(), TextWebRequestType.SUBMIT); + assertEquals(TextWebStatus.BUSY.toString(), result, "successful job submission"); + + FileAnalyserMock mock = (FileAnalyserMock) jobTracker.getJob(fo.getId()); + mock.setStatus(TextWebStatus.DONE); + mock.setLanguage("en"); + mock.setTermVectors(new ArrayList ()); + mock.setWordOrigins(new ArrayList ()); + + result = doRequest(fo.getId(), TextWebRequestType.QUERY); + assertEquals(TextWebStatus.DONE.toString(), result, "successful job completion"); + + assertNull((Object) jobTracker.getJob(fo.getId()), "job got removed"); + } } diff --git a/kx-web/src/test/resources/PostgresqlContainerSchemaFiles b/kx-web/src/test/resources/PostgresqlContainerSchemaFiles new file mode 100644 index 000000000..8fc81a1ec --- /dev/null +++ b/kx-web/src/test/resources/PostgresqlContainerSchemaFiles @@ -0,0 +1,5 @@ +# +# Configuration of PostgresqlContainerExtension: +# schema files for testing +# +00001.sql diff --git a/kx-web/src/test/resources/exampledocs/Document1.pdf b/kx-web/src/test/resources/exampledocs/Document1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b90e1194e2b58ac97def7c0601811640fcc3b261 GIT binary patch literal 180438 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νHxP9N=aEads<1qAE&=lE#;Ek1#=! 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 QcV=J9MuVXU)L*jy0E5mTa{vGU literal 0 HcmV?d00001 diff --git a/kx-web/src/test/resources/schema/00001.sql b/kx-web/src/test/resources/schema/00001.sql new file mode 100644 index 000000000..7f044ca8e --- /dev/null +++ b/kx-web/src/test/resources/schema/00001.sql @@ -0,0 +1,969 @@ +/* + * Leibniz Bioactives Cloud + * Init script for database postgres 12.6 + * + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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. + * + *========================================================= + * + * NOTE: TABLE CONSTRAINTS IN THIS FILE ARE RELAXED FOR TESTING PURPOSES: + * + * - collection_id may be NULL in table 'files' + * - user_id may be NULL in table 'files' + * + *========================================================= + * + * define global vars + */ +\set LBAC_SCHEMA_VERSION '\'00001\'' + +\set LBAC_SCHEMA lbac +\set LBAC_DATABASE lbac +\set LBAC_USER lbac +\set LBAC_PW lbac +-- quoted stuff -- +\set LBAC_SCHEMA_QUOTED '\'' :LBAC_SCHEMA '\'' +\set LBAC_DATABASE_QUOTED '\'' :LBAC_DATABASE '\'' +\set LBAC_USER_QUOTED '\'' :LBAC_USER '\'' +\set LBAC_PW_QUOTED '\'' :LBAC_PW '\'' + +/* + *========================================================= + * + * terminate all session from database lbac -- + * getting db exclusive -- + */ +SELECT pg_terminate_backend(pg_stat_activity.pid) +FROM pg_stat_activity +WHERE pg_stat_activity.datname = :LBAC_DATABASE_QUOTED + AND pid <> pg_backend_pid(); + +/* + * clean up + */ +-- the following statement fails if LBAC_USER is not known! +-- REASSIGN OWNED BY :LBAC_USER TO postgres; +DROP SCHEMA IF EXISTS :LBAC_SCHEMA CASCADE; +DROP DATABASE IF EXISTS :LBAC_DATABASE; +DROP USER IF EXISTS :LBAC_USER; + +/* + * (re-)create database objects + */ +-- roles -- +CREATE USER :LBAC_USER PASSWORD :LBAC_PW_QUOTED; +-- db -- +CREATE DATABASE :LBAC_DATABASE WITH ENCODING 'UTF8' OWNER :LBAC_USER; + +\connect :LBAC_DATABASE + +-- Bingo database extension (chemistry) -- +\i /opt/bingo/bingo_install.sql +GRANT USAGE ON SCHEMA bingo TO :LBAC_USER; +GRANT SELECT ON bingo.bingo_config TO :LBAC_USER; +GRANT SELECT ON bingo.bingo_tau_config TO :LBAC_USER; + +-- schema -- +CREATE SCHEMA AUTHORIZATION :LBAC_USER; + +-- adjust schema search path -- +ALTER USER :LBAC_USER SET search_path to :LBAC_SCHEMA,public; + +-- privileges -- +GRANT USAGE ON SCHEMA :LBAC_SCHEMA, public TO :LBAC_USER; +GRANT CONNECT, TEMPORARY, TEMP ON DATABASE :LBAC_DATABASE to :LBAC_USER; +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA :LBAC_SCHEMA to :LBAC_USER; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to :LBAC_USER; +REVOKE ALL ON ALL TABLES IN SCHEMA :LBAC_SCHEMA FROM public; + +-- check for usefull extensions and install it -- +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +\connect - :LBAC_USER + +BEGIN TRANSACTION; + +-- tables -- + +/* + * Nodes, Clouds, etc. + */ +CREATE TABLE nodes ( + id UUID NOT NULL PRIMARY KEY, + baseUrl VARCHAR NOT NULL, + institution VARCHAR NOT NULL, + local BOOLEAN NOT NULL DEFAULT FALSE, + publicNode BOOLEAN NOT NULL DEFAULT FALSE, + version VARCHAR NOT NULL DEFAULT '00001', + publickey VARCHAR NOT NULL DEFAULT 'dummy' +); + +CREATE TABLE clouds ( + id BIGSERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + UNIQUE (name) +); +INSERT INTO clouds (name) VALUES ('TESTCLOUD'); + +CREATE TABLE cloud_nodes ( + id BIGSERIAL NOT NULL PRIMARY KEY, + node_id UUID NOT NULL REFERENCES nodes(id) ON UPDATE CASCADE ON DELETE CASCADE, + cloud_id BIGINT NOT NULL REFERENCES clouds(id) ON UPDATE CASCADE ON DELETE CASCADE, + rank INTEGER NOT NULL DEFAULT 1, + publickey VARCHAR NOT NULL DEFAULT 'dummy', + failures INTEGER NOT NULL DEFAULT 0, + retrytime BIGINT NOT NULL DEFAULT 0, + UNIQUE (cloud_id, node_id) +); + + +/* definition of master node (will be skipped on master node) */ + +/* definition of local node */ +INSERT INTO nodes (id, baseUrl, institution, local) VALUES + ( '986ad1be-9a3b-4a70-8600-c489c2a00da4', + 'http://localhost/', + 'TEST', True); + +INSERT INTO cloud_nodes (node_id, cloud_id, rank) + SELECT '986ad1be-9a3b-4a70-8600-c489c2a00da4'::UUID AS node_id, id AS cloud_id, 10 AS rank + FROM clouds WHERE name='TESTCLOUD'; + +/* + * Admission + * users, groups, memberships + */ +CREATE TABLE usersGroups ( + id SERIAL NOT NULL PRIMARY KEY, + memberType VARCHAR(1) NOT NULL, + subSystemType INTEGER, + subSystemData VARCHAR, + modified TIMESTAMP DEFAULT now(), + node_id UUID REFERENCES nodes(id) ON UPDATE CASCADE ON DELETE CASCADE, + login VARCHAR, + name VARCHAR, + email VARCHAR, + password VARCHAR, + phone VARCHAR, + shortcut VARCHAR CHECK (shortcut ~ '^[A-Z]+$') UNIQUE +); + +CREATE TABLE memberships ( + id SERIAL NOT NULL PRIMARY KEY, + group_id INTEGER NOT NULL REFERENCES usersGroups (id) ON UPDATE CASCADE ON DELETE CASCADE, + member_id INTEGER NOT NULL REFERENCES usersGroups (id) ON UPDATE CASCADE ON DELETE CASCADE, + nested BOOLEAN NOT NULL DEFAULT FALSE, + UNIQUE(group_id, member_id) +); + +CREATE INDEX i_memberships_group ON memberships (group_id); +CREATE INDEX i_memberships_member ON memberships (member_id); + + +CREATE TABLE nestingpathsets ( + id SERIAL NOT NULL PRIMARY KEY, + membership_id INTEGER NOT NULL REFERENCES memberships(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +/* + * ToDo: Do some database sanitation for memberships. + * This cleanup / sanitation is necessary, when the membership table + * is manipulated out of band, because the table 'nestingpathsets' is not + * covered by referential integrity, i.e. deleting memberships via + * 'DELETE FROM memberships ...' will cover all other tables but not + * 'nestingpathsets'. + * + * CREATE OR REPLACE FUNCTION cleanNestingPathSets ( id INTEGER ) RETURNS INTEGER + * ... + * CREATE TRIGGER ... AFTER DELETE ON nestingpathset_memberships EXECUTE PROCEDURE cleanNestingPathSets(OLD.nestingpathset_id); + * + * Alternatively on could run the following SQL command manually: + * + * DELETE FROM nestingpathsets AS np USING (SELECT id FROM nestingpathsets + * EXCEPT SELECT nestingpathset_id FROM nestingpathset_memberships) AS e WHERE np.id=e.id; + * + */ + +CREATE TABLE nestingpathset_memberships ( + nestingpathsets_id INTEGER NOT NULL REFERENCES nestingpathsets(id) ON UPDATE CASCADE ON DELETE CASCADE, + memberships_id INTEGER NOT NULL REFERENCES memberships(id) ON UPDATE CASCADE ON DELETE CASCADE, + UNIQUE(nestingpathsets_id, memberships_id) +); + +/* + * Admission + * ACLs + */ +CREATE TABLE aclists ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + permCode INTEGER +); + +CREATE TABLE acentries ( + aclist_id INTEGER NOT NULL REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + member_id INTEGER NOT NULL REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + permRead BOOLEAN NOT NULL DEFAULT FALSE, + permEdit BOOLEAN NOT NULL DEFAULT FALSE, + permCreate BOOLEAN NOT NULL DEFAULT FALSE, + permDelete BOOLEAN NOT NULL DEFAULT FALSE, + permChown BOOLEAN NOT NULL DEFAULT FALSE, + permGrant BOOLEAN NOT NULL DEFAULT FALSE, + permSuper BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY(aclist_id, member_id) +); + +/* + * Info + */ +CREATE TABLE info ( + key VARCHAR NOT NULL PRIMARY KEY, + value VARCHAR, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +INSERT INTO info (key, value, owner_id, aclist_id) VALUES ('DBSchema Version', '00000', null, null); + +/* + * Collections and other distributed resources + */ +CREATE TABLE collections ( + id SERIAL NOT NULL PRIMARY KEY, + description VARCHAR, + name VARCHAR, + indexPath VARCHAR, + storagePath VARCHAR, + owner_id INTEGER NOT NULL REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + aclist_id INTEGER NOT NULL REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE files ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + filename VARCHAR NOT NULL, + hash VARCHAR, + created TIMESTAMP DEFAULT now(), + user_id INTEGER REFERENCES usersGroups (id) ON DELETE SET NULL, + collection_id INTEGER REFERENCES collections (id) ON UPDATE CASCADE ON DELETE CASCADE, + document_language VARCHAR NOT NULL DEFAULT 'en' +); + +CREATE TABLE topics ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + category VARCHAR, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + cloud_name VARCHAR NOT NULL DEFAULT 'cloudONE' REFERENCES clouds(name) ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE postings ( + id SERIAL NOT NULL PRIMARY KEY, + text VARCHAR, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + topic_id INTEGER REFERENCES topics(id) ON UPDATE CASCADE ON DELETE CASCADE, + created TIMESTAMP DEFAULT now() +); + + +/* + * Termvector table + */ +CREATE TABLE termvectors ( + wordroot VARCHAR NOT NULL, + file_id INTEGER NOT NULL REFERENCES files (id) ON UPDATE CASCADE ON DELETE CASCADE, + termfrequency INTEGER NOT NULL, + PRIMARY KEY(wordroot, file_id) +); +CREATE INDEX i_termvectors_file_id ON termvectors (file_id); + + +CREATE TABLE unstemmed_words( + stemmed_word VARCHAR NOT NULL, + file_id INTEGER NOT NULL REFERENCES files (id) ON UPDATE CASCADE ON DELETE CASCADE, + unstemmed_word VARCHAR NOT NULL, + PRIMARY KEY(stemmed_word,file_id, unstemmed_word), + FOREIGN KEY (stemmed_word, file_id) REFERENCES termvectors (wordroot, file_id) + ON UPDATE CASCADE ON DELETE CASCADE +); +CREATE INDEX i_unstemmed_words_stemmed_word ON unstemmed_words (stemmed_word); + + +/* + * CRIMSy development + */ +CREATE TABLE projecttypes ( + id INTEGER NOT NULL PRIMARY KEY, + type VARCHAR +); + +CREATE TABLE budgetreservationtypes ( + id INTEGER NOT NULL PRIMARY KEY +); + +CREATE TABLE materialtypes ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL +); + +CREATE TABLE materialdetailtypes ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL +); + +CREATE TABLE materialinformations ( + id INTEGER NOT NULL PRIMARY KEY, + materialtypeid INTEGER NOT NULL REFERENCES materialtypes(id), + materialdetailtypeid INTEGER NOT NULL REFERENCES materialdetailtypes(id), + mandatory BOOLEAN NOT NULL +); + +CREATE TABLE projects ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + budget NUMERIC, + budgetBlocked BOOLEAN default false, + projecttypeid INTEGER NOT NULL REFERENCES projecttypes(id), + owner_id INTEGER NOT NULL REFERENCES usersgroups(id), + aclist_id INTEGER NOT NULL REFERENCES aclists(id), + description VARCHAR, + ctime TIMESTAMP NOT NULL DEFAULT now(), + mtime TIMESTAMP NOT NULL DEFAULT now(), + deactivated BOOLEAN NOT NULL DEFAULT false +); +CREATE UNIQUE index project_index_name_unique ON projects (LOWER(name)); + +CREATE TABLE projecttemplates ( + id SERIAL NOT NULL PRIMARY KEY, + materialdetailtypeid INTEGER NOT NULL REFERENCES materialdetailtypes(id), + aclistid INTEGER NOT NULL REFERENCES aclists(id), + projectid INTEGER NOT NULL REFERENCES projects(id) +); + +CREATE TABLE budgetreservations ( + id SERIAL NOT NULL PRIMARY KEY, + startDate DATE, + endDate DATE , + type INTEGER NOT NULL REFERENCES budgetreservationtypes(id), + budget NUMERIC NOT NULL +); + + +INSERT INTO materialtypes VALUES(1,'STRUCTURE'); +INSERT INTO materialtypes VALUES(2,'MATERIAL_COMPOSITION'); +INSERT INTO materialtypes VALUES(3,'BIOMATERIAL'); +INSERT INTO materialtypes VALUES(4,'CONSUMABLE'); +INSERT INTO materialtypes VALUES(5,'SEQUENCE'); +INSERT INTO materialtypes VALUES(6,'TISSUE'); +INSERT INTO materialtypes VALUES(7,'TAXONOMY'); + + +INSERT INTO projecttypes VALUES(1,'CHEMICAL_PROJECT'); +INSERT INTO projecttypes VALUES(2,'IT_PROJECT'); +INSERT INTO projecttypes VALUES(3,'FINANCE_PROJECT'); +INSERT INTO projecttypes VALUES(4,'BIOLOGICAL_PROJECT'); +INSERT INTO projecttypes VALUES(5,'BIOCHEMICAL_PROJECT'); + + + +INSERT INTO materialdetailtypes VALUES(1,'COMMON_INFORMATION'); +INSERT INTO materialdetailtypes VALUES(2,'STRUCTURE_INFORMATION'); +INSERT INTO materialdetailtypes VALUES(3,'INDEX'); +INSERT INTO materialdetailtypes VALUES(4,'HAZARD_INFORMATION'); +INSERT INTO materialdetailtypes VALUES(5,'STORAGE_CLASSES'); +INSERT INTO materialdetailtypes VALUES(6,'TAXONOMY'); + +INSERT INTO materialInformations VALUES(1,1,1,false); +INSERT INTO materialInformations VALUES(2,1,2,false); +INSERT INTO materialInformations VALUES(3,1,3,false); +INSERT INTO materialInformations VALUES(4,1,4,false); +INSERT INTO materialInformations VALUES(5,1,5,false); + +INSERT INTO materialInformations VALUES(6,2,1,false); + +INSERT INTO materialInformations VALUES(7,3,1,false); +INSERT INTO materialInformations VALUES(8,3,6,false); + +INSERT INTO materialInformations VALUES(9,4,1,false); +INSERT INTO materialInformations VALUES(10,4,3,false); + + + +CREATE TABLE indextypes ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + javaclass VARCHAR, + UNIQUE(name)); + +CREATE TABLE materials ( + materialid SERIAL NOT NULL PRIMARY KEY, + materialTypeId INTEGER NOT NULL REFERENCES materialtypes(id), + ctime TIMESTAMP NOT NULL DEFAULT now(), + aclist_id INTEGER NOT NULL REFERENCES aclists(id), + owner_id INTEGER NOT NULL REFERENCES usersgroups(id), + deactivated BOOLEAN NOT NULL DEFAULT false, + projectId INTEGER REFERENCES projects(id)); + +CREATE TABLE material_indices ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + typeid INTEGER NOT NULL REFERENCES indextypes(id), + value VARCHAR NOT NULL, + language VARCHAR, + rank INTEGER); + +CREATE TABLE materialdetailrights ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + aclistid INTEGER NOT NULL REFERENCES aclists(id), + materialtypeid INTEGER NOT NULL REFERENCES materialdetailtypes(id)); + +CREATE TABLE molecules ( + id SERIAL NOT NULL PRIMARY KEY, + molecule TEXT); + +CREATE INDEX i_molecules_mol_idx ON molecules USING bingo_idx (molecule bingo.molecule); + +CREATE FUNCTION substructure (VARCHAR, VARCHAR) RETURNS BOOLEAN + AS $$ + -- + -- return true if the second structure is a substructure of the first. + -- + DECLARE + molecule ALIAS FOR $1; + substruct ALIAS FOR $2; + BEGIN + RETURN molecule @ ( substruct, '')::bingo.sub; + END; + $$ LANGUAGE plpgsql; + + +CREATE TABLE structures ( + id INTEGER PRIMARY KEY REFERENCES materials(materialid), + sumformula VARCHAR, + molarMass FLOAT, + exactMolarMass FLOAT, + moleculeID INTEGER REFERENCES molecules(id)); + +CREATE TABLE structures_hist ( + id INTEGER NOT NULL REFERENCES structures(id), + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + mtime TIMESTAMP NOT NULL, + digest VARCHAR, + sumformula_old VARCHAR, + sumformula_new VARCHAR, + molarMass_old FLOAT, + molarMass_new FLOAT, + exactMolarMass_old FLOAT, + exactMolarMass_new FLOAT, + moleculeId_old INTEGER REFERENCES molecules(id), + moleculeId_new INTEGER REFERENCES molecules(id), + PRIMARY KEY (id,mtime)); + +CREATE TABLE hazards ( + id INTEGER PRIMARY KEY, + name VARCHAR NOT NULL, + category INTEGER NOT NULL, + has_remarks BOOLEAN NOT NULL DEFAULT false); + +CREATE TABLE material_hazards ( + typeid INTEGER NOT NULL REFERENCES hazards(id), + materialid INTEGER NOT NULL REFERENCES materials(materialid), + remarks VARCHAR, + PRIMARY KEY(materialid,typeid)); + +CREATE TABLE storageclasses ( + id INTEGER PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE storageconditions ( + id INTEGER PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE storages ( + materialid INTEGER PRIMARY KEY REFERENCES materials(materialid), + storageclass INTEGER NOT NULL REFERENCES storageClasses(id), + description VARCHAR); + + +CREATE TABLE storageconditions_material ( + conditionId INTEGER NOT NULL REFERENCES storageconditions(id), + materialid INTEGER NOT NULL REFERENCES materials(materialid), + PRIMARY KEY (conditionId,materialid) +); + +insert into storageconditions(id,name)values(1,'moistureSensitive'); +insert into storageconditions(id,name)values(2,'keepMoisture'); +insert into storageconditions(id,name)values(3,'lightSensitive'); +insert into storageconditions(id,name)values(4,'underInertGas'); +insert into storageconditions(id,name)values(5,'acidSensitive'); +insert into storageconditions(id,name)values(6,'alkaliSensitive'); +insert into storageconditions(id,name)values(7,'KeepAwayFromOxidants'); +insert into storageconditions(id,name)values(8,'frostSensitive'); +insert into storageconditions(id,name)values(9,'keepCool'); +insert into storageconditions(id,name)values(10,'keepFrozen'); +insert into storageconditions(id,name)values(11,'storeUnderMinus40Degrees'); +insert into storageconditions(id,name)values(12,'storeUnderMinus80Degrees'); + +insert into indextypes(name,javaclass)values('name',null); +insert into indextypes(name,javaclass)values('GESTIS/ZVG',null); +insert into indextypes(name,javaclass)values('CAS/RN',null); +insert into indextypes(name,javaclass)values('Carl Roth Sicherheitsdatenblatt',null); + +insert into storageclasses(id,name)values(1,'1'); +insert into storageclasses(id,name)values(2,'2A'); +insert into storageclasses(id,name)values(3,'2B'); +insert into storageclasses(id,name)values(4,'3'); +insert into storageclasses(id,name)values(5,'4.1A'); +insert into storageclasses(id,name)values(6,'4.1B'); +insert into storageclasses(id,name)values(7,'4.2'); +insert into storageclasses(id,name)values(8,'4.3'); +insert into storageclasses(id,name)values(9,'5.1A'); +insert into storageclasses(id,name)values(10,'5.1B'); +insert into storageclasses(id,name)values(11,'5.1C'); +insert into storageclasses(id,name)values(12,'6.1A'); +insert into storageclasses(id,name)values(13,'6.1B'); +insert into storageclasses(id,name)values(14,'6.1C'); +insert into storageclasses(id,name)values(15,'6.1D'); +insert into storageclasses(id,name)values(16,'6.2'); +insert into storageclasses(id,name)values(17,'7'); +insert into storageclasses(id,name)values(18,'8A'); +insert into storageclasses(id,name)values(19,'8B'); +insert into storageclasses(id,name)values(20,'10'); +insert into storageclasses(id,name)values(21,'11'); +insert into storageclasses(id,name)values(22,'12'); +insert into storageclasses(id,name)values(23,'13'); + +insert into hazards(id,name,category,has_remarks)values(1,'GHS01',1,false); +insert into hazards(id,name,category,has_remarks)values(2,'GHS02',1,false); +insert into hazards(id,name,category,has_remarks)values(3,'GHS03',1,false); +insert into hazards(id,name,category,has_remarks)values(4,'GHS04',1,false); +insert into hazards(id,name,category,has_remarks)values(5,'GHS05',1,false); +insert into hazards(id,name,category,has_remarks)values(6,'GHS06',1,false); +insert into hazards(id,name,category,has_remarks)values(7,'GHS07',1,false); +insert into hazards(id,name,category,has_remarks)values(8,'GHS08',1,false); +insert into hazards(id,name,category,has_remarks)values(9,'GHS09',1,false); +insert into hazards(id,name,category,has_remarks)values(10,'HS',2,true); +insert into hazards(id,name,category,has_remarks)values(11,'PS',2,true); +insert into hazards(id,name,category,has_remarks)values(12,'S1',3,false); +insert into hazards(id,name,category,has_remarks)values(13,'S2',3,false); +insert into hazards(id,name,category,has_remarks)values(14,'S3',3,false); +insert into hazards(id,name,category,has_remarks)values(15,'S4',3,false); +insert into hazards(id,name,category,has_remarks)values(16,'R1',4,false); +insert into hazards(id,name,category,has_remarks)values(17,'C1',5,true); +insert into hazards(id,name,category,has_remarks)values(18,'GHS10',1,false); +insert into hazards(id,name,category,has_remarks)values(19,'GHS11',1,false); +insert into hazards(id,name,category,has_remarks)values(20,'GMO',6,false); + +CREATE TABLE materials_hist ( + materialid INTEGER NOT NULL REFERENCES materials(materialid), + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + mDate TIMESTAMP, + digest VARCHAR, + action VARCHAR, + aclistid_old INTEGER REFERENCES aclists(id), + aclistid_new INTEGER REFERENCES aclists(id), + projectid_old INTEGER REFERENCES projects(id), + projectid_new INTEGER REFERENCES projects(id), + ownerid_old INTEGER REFERENCES usersgroups(id), + ownerid_new INTEGER REFERENCES usersgroups(id), + PRIMARY KEY(materialid,mDate)); + +CREATE TABLE material_indices_hist ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + typeid INTEGER NOT NULL REFERENCES indextypes(id), + mDate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + value_old VARCHAR, + value_new VARCHAR, + rank_old INTEGER, + rank_new INTEGER, + language_old VARCHAR, + language_new VARCHAR); + +CREATE TABLE material_hazards_hist ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + mdate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + typeid_old INTEGER REFERENCES hazards(id), + typeid_new INTEGER REFERENCES hazards(id), + remarks_old VARCHAR, + remarks_new VARCHAR); + +CREATE TABLE storages_hist ( + materialid INTEGER NOT NULL REFERENCES materials(materialid), + mdate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + description_old VARCHAR, + description_new VARCHAR, + storageclass_old INTEGER REFERENCES storageclasses(id), + storageclass_new INTEGER REFERENCES storageclasses(id), + PRIMARY KEY(materialid,mdate)); + + +CREATE TABLE storagesconditions_storages_hist ( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + mdate TIMESTAMP NOT NULL, + actorId INTEGER NOT NULL REFERENCES usersgroups(id), + digest VARCHAR, + conditionId_old INTEGER, + conditionId_new INTEGER); + +/* + * transportable: container can change place (ie change the parent in the hierarchy) + * uniq_name: multiple containers can share the same name if they have different parents + */ +CREATE TABLE containertypes( + name varchar NOT NULL PRIMARY KEY, + description varchar, + transportable BOOLEAN NOT NULL DEFAULT true, + unique_name BOOLEAN NOT NULL DEFAULT true, + rank integer not null); + +CREATE TABLE containers( + id SERIAL NOT NULL PRIMARY KEY, + parentcontainer INTEGER REFERENCES containers(id), + label VARCHAR NOT NULL, + projectid INTEGER REFERENCES projects(id), + rows INTEGER, + columns INTEGER, + type VARCHAR NOT NULL REFERENCES containertypes(name), + firearea VARCHAR, + gmosafetylevel VARCHAR, + barcode VARCHAR, + swapdimensions BOOLEAN NOT NULL DEFAULT FALSE, + zerobased BOOLEAN NOT NULL DEFAULT FALSE, + deactivated BOOLEAN NOT NULL DEFAULT false); + +CREATE TABLE nested_containers( + sourceid INTEGER NOT NULL REFERENCES containers(id), + targetid INTEGER NOT NULL REFERENCES containers(id), + nested BOOLEAN NOT NULL, + PRIMARY KEY(sourceid,targetid)); + +CREATE TABLE solvents( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL); + + +CREATE TABLE items( + id SERIAL NOT NULL PRIMARY KEY, + materialid INTEGER NOT NULL REFERENCES materials(materialid), + amount FLOAT NOT NULL, + articleid INTEGER, + projectid INTEGER REFERENCES projects(id), + concentration FLOAT, + concentrationunit VARCHAR, + unit VARCHAR, + purity VARCHAR, + solventid INTEGER REFERENCES solvents(id), + description VARCHAR, + owner_id INTEGER NOT NULL REFERENCES usersgroups(id), + containersize FLOAT, + containertype VARCHAR REFERENCES containertypes(name), + containerid INTEGER REFERENCES containers(id), + ctime TIMESTAMP NOT NULL DEFAULT now(), + expiry_date TIMESTAMP, + aclist_id INTEGER NOT NULL, + label VARCHAR, + parent_id INTEGER REFERENCES items(id) +); + +CREATE TABLE item_positions( + id SERIAL NOT NULL PRIMARY KEY, + itemid INTEGER NOT NULL REFERENCES items(id) ON UPDATE CASCADE ON DELETE CASCADE, + containerid INTEGER NOT NULL REFERENCES containers(id) ON UPDATE CASCADE ON DELETE CASCADE, + itemrow INTEGER, + itemcol INTEGER, + UNIQUE(itemid), + UNIQUE(containerid, itemrow, itemcol) +); + +CREATE TABLE items_history( + itemid INTEGER NOT NULL REFERENCES items(id), + mdate TIMESTAMP NOT NULL, + actorid INTEGER NOT NULL REFERENCES usersgroups(id), + action VARCHAR NOT NULL, + projectid_old INTEGER REFERENCES projects(id), + projectid_new INTEGER REFERENCES projects(id), + concentration_old FLOAT, + concentration_new FLOAT, + purity_old VARCHAR, + purity_new VARCHAR, + description_new VARCHAR, + description_old VARCHAR, + amount_old FLOAT, + amount_new FLOAT, + owner_old INTEGER REFERENCES usersgroups(id), + owner_new INTEGER REFERENCES usersgroups(id), + parent_containerid_new INTEGER REFERENCES containers(id), + parent_containerid_old INTEGER REFERENCES containers(id), + aclistid_old INTEGER REFERENCES aclists(id), + aclistid_new INTEGER REFERENCES aclists(id), + PRIMARY KEY(itemid,actorid,mdate)); + +CREATE TABLE item_positions_history( + id SERIAL PRIMARY KEY, + itemid INTEGER NOT NULL REFERENCES items(id), + containerid INTEGER NOT NULL REFERENCES containers(id), + mdate TIMESTAMP NOT NULL, + actorid INTEGER NOT NULL REFERENCES usersgroups(id), + row_old INTEGER, + row_new INTEGER, + col_old INTEGER, + col_new INTEGER +); + +insert into containertypes(name,description,rank,transportable,unique_name)values('ROOM',null,100,false,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('CUPBOARD',null,90,false,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('FREEZER',null,90,false,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('FRIDGE',null,90,false,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('TRAY',null,60,true,false); +insert into containertypes(name,description,rank,transportable,unique_name)values('WELLPLATE',null,50,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('GLASS_BOTTLE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_BOTTLE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('GLASS_VIAL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_VIAL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('GLASS_AMPOULE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_AMPOULE',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('STEEL_BARREL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_BARREL',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('STEEL_CONTAINER',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_CONTAINER',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('CARTON',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_BAG',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PLASTIC_SACK',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('PAPER_BAG',null,0,true,true); +insert into containertypes(name,description,rank,transportable,unique_name)values('COMPRESSED_GAS_CYLINDER',null,0,true,true); + +CREATE TABLE taxonomy_level( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + rank INTEGER NOT NULL); + +CREATE TABLE taxonomy( + id INTEGER NOT NULL PRIMARY KEY REFERENCES materials(materialid), + level INTEGER NOT NULL REFERENCES taxonomy_level(id)); + +CREATE TABLE effective_taxonomy( + id SERIAL NOT NULL PRIMARY KEY, + taxoid INTEGER NOT NULL REFERENCES taxonomy(id), + parentid INTEGER NOT NULL REFERENCES taxonomy(id)); + +CREATE TABLE taxonomy_history( + taxonomyid INTEGER NOT NULL REFERENCES materials(materialid), + actorid INTEGER NOT NULL REFERENCES usersgroups(id), + mdate TIMESTAMP NOT NULL, + action VARCHAR NOT NULL, + digest VARCHAR, + level_old INTEGER, + level_new INTEGER, + parentid_old INTEGER, + parentid_new INTEGER, + PRIMARY KEY(taxonomyid,actorid,mdate)); + +CREATE TABLE tissues( + id INTEGER NOT NULL PRIMARY KEY REFERENCES materials(materialid), + taxoid INTEGER NOT NULL REFERENCES taxonomy(id) +); + +CREATE TABLE biomaterial( + id INTEGER NOT NULL PRIMARY KEY REFERENCES materials(materialid), + taxoid INTEGER NOT NULL REFERENCES taxonomy(id), + tissueid INTEGER REFERENCES tissues(id) +); + +CREATE TABLE biomaterial_history( + id INTEGER NOT NULL REFERENCES biomaterial(id), + actorid INTEGER NOT NULL REFERENCES usersGroups(id), + mtime TIMESTAMP NOT NULL, + digest VARCHAR, + action VARCHAR NOT NULL, + tissueid_old INTEGER REFERENCES tissues(id), + tissueid_new INTEGER REFERENCES tissues(id), + taxoid_old INTEGER REFERENCES taxonomy(id), + taxoid_new INTEGER REFERENCES taxonomy(id), + PRIMARY KEY(id,actorid,mtime) +); + +INSERT INTO taxonomy_level VALUES(1,'domain',100); +INSERT INTO taxonomy_level VALUES(2,'kingdom',200); +INSERT INTO taxonomy_level VALUES(3,'subkingdom',300); +INSERT INTO taxonomy_level VALUES(4,'division',400); +INSERT INTO taxonomy_level VALUES(5,'subdivision',500); +INSERT INTO taxonomy_level VALUES(6,'class',600); +INSERT INTO taxonomy_level VALUES(7,'subclass',700); +INSERT INTO taxonomy_level VALUES(8,'superorder',800); +INSERT INTO taxonomy_level VALUES(9,'order',900); +INSERT INTO taxonomy_level VALUES(10,'suborder',1000); +INSERT INTO taxonomy_level VALUES(11,'family',1100); +INSERT INTO taxonomy_level VALUES(12,'subfamily',1200); +INSERT INTO taxonomy_level VALUES(13,'tribe',1300); +INSERT INTO taxonomy_level VALUES(14,'genus',1400); +INSERT INTO taxonomy_level VALUES(15,'section',1500); +INSERT INTO taxonomy_level VALUES(16,'series',1600); +INSERT INTO taxonomy_level VALUES(17,'aggregate',1700); +INSERT INTO taxonomy_level VALUES(18,'species',1800); +INSERT INTO taxonomy_level VALUES(19,'subspecies',1900); +INSERT INTO taxonomy_level VALUES(20,'variety',2000); +INSERT INTO taxonomy_level VALUES(21,'form',2100); + + + +/* + * ToDo: xxxxx add ON UPDATE CASCADE/ ON DELETE CASCADE ? + */ + +CREATE TABLE folders ( + folderid SERIAL NOT NULL PRIMARY KEY, + name VARCHAR, + parentid INTEGER REFERENCES folders(folderid) ON UPDATE RESTRICT ON DELETE RESTRICT, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE RESTRICT ON DELETE RESTRICT, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE RESTRICT ON DELETE RESTRICT, + ctime TIMESTAMP NOT NULL DEFAULT now(), + projectid INTEGER REFERENCES projects(id) ON UPDATE RESTRICT ON DELETE RESTRICT +); +INSERT INTO folders (name) VALUES ('default'); + +CREATE TABLE experiments ( + experimentid SERIAL NOT NULL PRIMARY KEY, + code VARCHAR, + description VARCHAR, + template BOOLEAN NOT NULL DEFAULT FALSE, + folderid INTEGER NOT NULL REFERENCES folders(folderid) ON UPDATE RESTRICT ON DELETE RESTRICT, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + ctime TIMESTAMP NOT NULL DEFAULT now(), + projectid INTEGER REFERENCES projects(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE exp_records ( + exprecordid BIGSERIAL NOT NULL PRIMARY KEY, + experimentid INTEGER NOT NULL REFERENCES experiments (experimentid) ON UPDATE CASCADE ON DELETE CASCADE, + changetime TIMESTAMP, + creationtime TIMESTAMP, + type INTEGER, + revision INTEGER NOT NULL DEFAULT 1, + next BIGINT DEFAULT NULL REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE SET NULL +); + +/* + * Note: currently either materialid or itemid must be set + * + * ToDo: + * - add references to documents, users, etc. + * - additional indexing for payload column (works + * only if payload is of JSON type; see example below) + */ +CREATE TABLE linked_data ( + recordid BIGSERIAL NOT NULL PRIMARY KEY, + exprecordid BIGINT NOT NULL + REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + materialid INTEGER + REFERENCES materials(materialid) ON UPDATE CASCADE ON DELETE CASCADE, + itemid INTEGER CHECK (COALESCE(materialid, itemid,fileid) IS NOT NULL) + REFERENCES items(id) ON UPDATE CASCADE ON DELETE CASCADE, + fileid INTEGER REFERENCES files(id) ON UPDATE CASCADE ON DELETE CASCADE, + rank INTEGER DEFAULT 0, + type INTEGER NOT NULL, + payload VARCHAR +); + +/* + * B-tree index example: + * CREATE INDEX i_exp_linked_data_val ON exp_linked_data (((payload->>'val')::DOUBLE PRECISION)) + * WHERE (payload->>'val') IS NOT NULL; + */ + +CREATE TABLE exp_assays ( + exprecordid BIGINT NOT NULL PRIMARY KEY REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + outcometype INTEGER NOT NULL, + remarks VARCHAR, + units VARCHAR +); + +/* ToDo: xxxxx create fulltext index on exp_texts! */ +CREATE TABLE exp_texts ( + exprecordid BIGINT NOT NULL PRIMARY KEY REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + text VARCHAR +); + +/** + * - Agency: job scheduling + * - barcode printing + */ +CREATE TABLE jobs ( + jobid SERIAL NOT NULL PRIMARY KEY, + input BYTEA, + jobdate TIMESTAMP NOT NULL DEFAULT now(), + jobtype INTEGER NOT NULL, + output BYTEA, + ownerid INTEGER REFERENCES usersgroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + queue VARCHAR NOT NULL, + status INTEGER NOT NULL +); + +CREATE TABLE printers ( + queue VARCHAR NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + aclistid INTEGER NOT NULL REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + config VARCHAR NOT NULL DEFAULT '', + contact VARCHAR NOT NULL DEFAULT '', + driver VARCHAR NOT NULL, + model VARCHAR NOT NULL DEFAULT '', + ownerid INTEGER NOT NULL REFERENCES usersgroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + place VARCHAR NOT NULL DEFAULT '', + status INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE labels ( + id SERIAL NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL DEFAULT '', + description VARCHAR NOT NULL DEFAULT '', + labeltype VARCHAR NOT NULL DEFAULT '', + printermodel VARCHAR NOT NULL DEFAULT '', + config VARCHAR NOT NULL DEFAULT '' +); + +CREATE TABLE preferences ( + id SERIAL NOT NULL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES usersgroups(id) ON UPDATE CASCADE ON DELETE CASCADE, + key VARCHAR NOT NULL, + value VARCHAR, + UNIQUE (user_id, key) +); + +CREATE TABLE images ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES exp_records(exprecordid) ON UPDATE CASCADE ON DELETE CASCADE, + title VARCHAR, + preview VARCHAR, + image VARCHAR, + aclist_id INTEGER REFERENCES aclists(id) ON UPDATE CASCADE ON DELETE CASCADE, + owner_id INTEGER REFERENCES usersGroups(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +/* + * finally set DBSchema Version + */ +UPDATE lbac.info SET value=:LBAC_SCHEMA_VERSION WHERE key='DBSchema Version'; +COMMIT; + diff --git a/kx-web/src/test/resources/test-persistence.xml b/kx-web/src/test/resources/test-persistence.xml new file mode 100644 index 000000000..18d4a0e07 --- /dev/null +++ b/kx-web/src/test/resources/test-persistence.xml @@ -0,0 +1,55 @@ + + + + + + + + LBAC api + apiDS + + org.hibernate.jpa.HibernatePersistenceProvider + + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity + + true + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 65d9103bd..dba275bf0 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ pom + crimsy-test crimsy-api agency-api agency diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java index a8bd60b61..555077f22 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java @@ -69,7 +69,6 @@ public class DocumentSearchServiceTest extends TestBase { protected Collection col; - protected String exampleDocsRootFolder = "target/test-classes/exampledocs/"; protected User publicUser; protected AsyncContextMock asynContext; private DocumentCreator documentCreator; diff --git a/ui/src/test/resources/PostgresqlContainerSchemaFiles b/ui/src/test/resources/PostgresqlContainerSchemaFiles new file mode 100644 index 000000000..d2baa73fa --- /dev/null +++ b/ui/src/test/resources/PostgresqlContainerSchemaFiles @@ -0,0 +1,7 @@ +# +# Configuration of PostgresqlContainerExtension: +# schema files for testing +# +00001.sql +00002.sql +00003.sql From 9f2c6803d30e8ab30c324d9a3b2f111147ac03d8 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Sun, 1 Oct 2023 13:13:23 +0200 Subject: [PATCH 13/28] complementing the tests for kx-web --- .../kx/termvector/StemmedWordOrigin.java | 40 ++++++-- .../kx/termvector/StemmedWordOriginTest.java | 46 +++++++++ kx-web/pom.xml | 3 +- .../de/ipb_halle/kx/service/FileAnalyser.java | 6 +- .../kx/service/FileAnalyserTest.java | 95 +++++++++++++----- .../resources/exampledocs/Document_FR.docx | Bin .../exampledocs/Document_wordStemming.docx | Bin .../exampledocs/IPB_Jahresbericht_2004.pdf | Bin .../exampledocs/ShortNumberExample.docx | Bin .../resources/exampledocs/ShortRealText.docx | Bin .../resources/exampledocs/TestTabelle.xlsx | Bin 0 -> 10370 bytes .../ipb_halle/lbac/file/UploadToColTest.java | 30 ++---- 12 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 kx-api/src/test/java/de/ipb_halle/kx/termvector/StemmedWordOriginTest.java rename {ui => kx-web}/src/test/resources/exampledocs/Document_FR.docx (100%) rename {ui => kx-web}/src/test/resources/exampledocs/Document_wordStemming.docx (100%) rename {ui => kx-web}/src/test/resources/exampledocs/IPB_Jahresbericht_2004.pdf (100%) rename {ui => kx-web}/src/test/resources/exampledocs/ShortNumberExample.docx (100%) rename {ui => kx-web}/src/test/resources/exampledocs/ShortRealText.docx (100%) create mode 100644 kx-web/src/test/resources/exampledocs/TestTabelle.xlsx diff --git a/kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java b/kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java index 67b9d7f34..5129b4a82 100644 --- a/kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java +++ b/kx-api/src/main/java/de/ipb_halle/kx/termvector/StemmedWordOrigin.java @@ -17,6 +17,8 @@ */ package de.ipb_halle.kx.termvector; +import java.util.Objects; + /** * * @see TermVectorEntity for SqlResultSetMapping @@ -35,20 +37,44 @@ public StemmedWordOrigin(String wordroot, String original) { public StemmedWordOrigin() { } - public String getStemmedWord() { - return stemmedWord; - } - - public void setStemmedWord(String s) { - this.stemmedWord = s; + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final StemmedWordOrigin other = (StemmedWordOrigin) obj; + if (!Objects.equals(this.originalWord, other.originalWord)) { + return false; + } + if (!Objects.equals(this.stemmedWord, other.stemmedWord)) { + return false; + } + return true; } public String getOriginalWord() { return originalWord; } + + public String getStemmedWord() { + return stemmedWord; + } + + @Override + public int hashCode() { + return Objects.hashCode(originalWord) + Objects.hashCode(stemmedWord); + } + public void setOriginalWord(String o) { this.originalWord = o; } - + public void setStemmedWord(String s) { + this.stemmedWord = s; + } } diff --git a/kx-api/src/test/java/de/ipb_halle/kx/termvector/StemmedWordOriginTest.java b/kx-api/src/test/java/de/ipb_halle/kx/termvector/StemmedWordOriginTest.java new file mode 100644 index 000000000..1c13f9bc1 --- /dev/null +++ b/kx-api/src/test/java/de/ipb_halle/kx/termvector/StemmedWordOriginTest.java @@ -0,0 +1,46 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.termvector; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; + +/** + * + * @author fbroda + */ +public class StemmedWordOriginTest { + + @Test + public void test001_equals() { + StemmedWordOrigin foo = new StemmedWordOrigin("foo", "foo"); + StemmedWordOrigin bar = new StemmedWordOrigin("bar", "bar"); + StemmedWordOrigin foobar = new StemmedWordOrigin("foobar", "foo"); + StemmedWordOrigin barfoo = new StemmedWordOrigin("foo", "foobar"); + StemmedWordOrigin foofoo = new StemmedWordOrigin("fo" + "o", "f" + "oo"); + Assert.assertFalse(foo.equals(null)); + Assert.assertFalse(foo.equals("foobar")); + Assert.assertFalse(foo.equals(bar)); + Assert.assertFalse(foo.equals(foobar)); + Assert.assertFalse(foo.equals(barfoo)); + Assert.assertTrue(foo.equals(foofoo)); + + Assert.assertFalse(foo.hashCode() == bar.hashCode()); + Assert.assertTrue(foo.hashCode() == foofoo.hashCode()); + } +} diff --git a/kx-web/pom.xml b/kx-web/pom.xml index 055f954f7..9940162e8 100644 --- a/kx-web/pom.xml +++ b/kx-web/pom.xml @@ -147,9 +147,8 @@ maven-war-plugin 2.4 - web true - ui + kx true diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java index 4640ae86f..c182bcfb7 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/FileAnalyser.java @@ -102,13 +102,13 @@ public TextWebStatus getStatus() { } public List getTermVector() { - List termVectors = new ArrayList<>(); + List termVector = new ArrayList<>(); @SuppressWarnings("unchecked") Map termvectorMap = (Map) parseTool.getFilterData().getValue(TermVectorFilter.TERM_VECTOR); for (String tv : termvectorMap.keySet()) { - termVectors.add(new TermVector(tv, fileObject.getId(), termvectorMap.get(tv))); + termVector.add(new TermVector(tv, fileObject.getId(), termvectorMap.get(tv))); } - return termVectors; + return termVector; } public List getWordOrigins() { diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java index 39bbe29af..9da196eec 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java @@ -22,8 +22,13 @@ import de.ipb_halle.kx.termvector.StemmedWordOrigin; import de.ipb_halle.kx.termvector.TermVector; import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.Assert; import org.junit.jupiter.api.Test; @@ -35,18 +40,28 @@ public class FileAnalyserTest { protected String examplaDocsRootFolder = "target/test-classes/exampledocs/"; - public FileObject createMockFile(Integer id, String path) { + private FileObject createMockFile(Integer id, String path) { FileObject fileObject = new FileObject(); fileObject.setId(id); fileObject.setFileLocation(path); return fileObject; } - @Test - public void test001_analyseEnglishPdf() throws FileNotFoundException, Exception { + private FileAnalyser setupAnalyser(String fileName) { FileAnalyser analyser = new FileAnalyser(); - analyser.setFileObject(createMockFile(1, examplaDocsRootFolder + "Document1.pdf")); + analyser.setFileObject(createMockFile(1, examplaDocsRootFolder + fileName)); analyser.run(); + return analyser; + } + + private boolean compareTermVectors(TermVector tv1, TermVector tv2) { + return tv1.equals(tv2) + && (tv1.getTermFrequency() == tv2.getTermFrequency()); + } + + @Test + public void test001_analyseEnglishPdf() throws FileNotFoundException, Exception { + FileAnalyser analyser = setupAnalyser("Document1.pdf"); List tvs = null; try { tvs = analyser.getTermVector(); @@ -85,11 +100,10 @@ public void test001_analyseEnglishPdf() throws FileNotFoundException, Exception Assert.assertEquals("undefined", analyser.getLanguage()); } -/* + @Test public void test002_analyseGermanXls() throws FileNotFoundException { - FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); - analyser.analyseFile(examplaDocsRootFolder + "TestTabelle.xlsx", 1); + FileAnalyser analyser = setupAnalyser("TestTabelle.xlsx"); //Assert.assertEquals(20, analyser.getWordOrigins().size()); //Assert.assertEquals(20, analyser.getTermVector().size()); Assert.assertEquals("de", analyser.getLanguage()); @@ -97,8 +111,7 @@ public void test002_analyseGermanXls() throws FileNotFoundException { @Test public void test003_analyseFrenchWord() throws FileNotFoundException { - FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); - analyser.analyseFile(examplaDocsRootFolder + "Document_FR.docx", 1); + FileAnalyser analyser = setupAnalyser("Document_FR.docx"); // Assert.assertEquals(210, analyser.getWordOrigins().size()); // Assert.assertEquals(210, analyser.getTermVector().size()); Assert.assertEquals("fr", analyser.getLanguage()); @@ -106,17 +119,13 @@ public void test003_analyseFrenchWord() throws FileNotFoundException { @Test public void test004_analyseStemming() throws FileNotFoundException { - FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); - analyser.analyseFile(examplaDocsRootFolder + "Document_wordStemming.docx", 1); + FileAnalyser analyser = setupAnalyser("Document_wordStemming.docx"); // Assert.assertEquals(12, analyser.getWordOrigins().size()); // Assert.assertEquals(12, analyser.getTermVector().size()); for (StemmedWordOrigin swo : analyser.getWordOrigins()) { if (swo.getStemmedWord().equals("saur")) { - Set originWords = swo.getOriginalWord(); - Assert.assertEquals(2, originWords.size()); - for (String s : originWords) { - Assert.assertTrue(s.equals("säuren") || s.equals("säure")); - } + Assert.assertTrue(swo.getOriginalWord().equals("säuren") + || swo.getOriginalWord().equals("säure")); } } Assert.assertEquals("de", analyser.getLanguage()); @@ -124,17 +133,57 @@ public void test004_analyseStemming() throws FileNotFoundException { @Test public void test005_checkUniqueWordOrigins() throws FileNotFoundException, Exception { - FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); - analyser.analyseFile(examplaDocsRootFolder + "IPB_Jahresbericht_2004.pdf", 1); - List tvs = analyser.getTermVector(); - // Assert.assertEquals(5428, tvs.size()); + FileAnalyser analyser = setupAnalyser("IPB_Jahresbericht_2004.pdf"); + Assert.assertEquals(5319, analyser.getTermVector().size()); Assert.assertEquals("de", analyser.getLanguage()); } @Test public void test006_analyseRealWorldText() throws FileNotFoundException, Exception { - FileAnalyser analyser = new FileAnalyser(FilterDefinitionInputStreamFactory.getFilterDefinition()); - analyser.analyseFile(examplaDocsRootFolder + "ShortRealText.docx", 1); + FileAnalyser analyser = setupAnalyser("ShortRealText.docx"); + + Map expectedTV = Arrays.asList( + // TermVector(stem, fileId, frequency) + new TermVector("rafft", 1, 1), + new TermVector("menschheit", 1, 1), + new TermVector("gurk", 1, 1), + new TermVector("nikotin", 1, 1), + new TermVector("alkohol", 1, 1), + new TermVector("hopfenkaltschal", 1, 1), + new TermVector("schnitzel", 1, 1), + new TermVector("saur", 1, 1), + new TermVector("grundnahrungsmittel", 1, 1), + new TermVector("halb", 1, 1)) + .stream() + .collect(Collectors.toMap(TermVector::getWordRoot, Function.identity())); + + List resultTV = analyser.getTermVector(); + Assert.assertEquals(expectedTV.size(), resultTV.size()); + + for (TermVector tv : resultTV) { + Assert.assertTrue(compareTermVectors(tv, expectedTV.get(tv.getWordRoot()))); + } + + Set expectedSWO = new HashSet<> (); + expectedSWO.addAll(Arrays.asList( + new StemmedWordOrigin("rafft", "rafft"), + new StemmedWordOrigin("menschheit", "menschheit"), + new StemmedWordOrigin("gurk", "gurken"), + new StemmedWordOrigin("nikotin", "nikotin"), + new StemmedWordOrigin("alkohol", "alkohol"), + new StemmedWordOrigin("hopfenkaltschal", "hopfenkaltschale"), + new StemmedWordOrigin("schnitzel", "schnitzel"), + new StemmedWordOrigin("saur", "saure"), + new StemmedWordOrigin("grundnahrungsmittel", "grundnahrungsmittel"), + new StemmedWordOrigin("halb", "halbe"))); + Set resultSWO = new HashSet<> (); + resultSWO.addAll(analyser.getWordOrigins()); + Assert.assertTrue(expectedSWO.equals(resultSWO)); + } + + @Test + public void test007_smallNumberFilteringTest() throws Exception { + FileAnalyser analyser = setupAnalyser("ShortNumberExample.docx"); + Assert.assertEquals(analyser.getTermVector().size(), 4); } -*/ } diff --git a/ui/src/test/resources/exampledocs/Document_FR.docx b/kx-web/src/test/resources/exampledocs/Document_FR.docx similarity index 100% rename from ui/src/test/resources/exampledocs/Document_FR.docx rename to kx-web/src/test/resources/exampledocs/Document_FR.docx diff --git a/ui/src/test/resources/exampledocs/Document_wordStemming.docx b/kx-web/src/test/resources/exampledocs/Document_wordStemming.docx similarity index 100% rename from ui/src/test/resources/exampledocs/Document_wordStemming.docx rename to kx-web/src/test/resources/exampledocs/Document_wordStemming.docx diff --git a/ui/src/test/resources/exampledocs/IPB_Jahresbericht_2004.pdf b/kx-web/src/test/resources/exampledocs/IPB_Jahresbericht_2004.pdf similarity index 100% rename from ui/src/test/resources/exampledocs/IPB_Jahresbericht_2004.pdf rename to kx-web/src/test/resources/exampledocs/IPB_Jahresbericht_2004.pdf diff --git a/ui/src/test/resources/exampledocs/ShortNumberExample.docx b/kx-web/src/test/resources/exampledocs/ShortNumberExample.docx similarity index 100% rename from ui/src/test/resources/exampledocs/ShortNumberExample.docx rename to kx-web/src/test/resources/exampledocs/ShortNumberExample.docx diff --git a/ui/src/test/resources/exampledocs/ShortRealText.docx b/kx-web/src/test/resources/exampledocs/ShortRealText.docx similarity index 100% rename from ui/src/test/resources/exampledocs/ShortRealText.docx rename to kx-web/src/test/resources/exampledocs/ShortRealText.docx diff --git a/kx-web/src/test/resources/exampledocs/TestTabelle.xlsx b/kx-web/src/test/resources/exampledocs/TestTabelle.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..abd900e4416c8e1bc412848a18dace025a6b99de GIT binary patch literal 10370 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

- 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 G&;AGdw6?wg literal 0 HcmV?d00001 diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java index 60cb26fac..d1a143259 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java @@ -88,7 +88,7 @@ public void test001_fileUploadTest() throws Exception { fileObjectService, publicUser, new AsyncContextMock( - new File(exampleDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), + new File(exampleDocsRootFolder + "Document1.pdf"), col.getName()), collectionService, "target/test-classes/collections"); @@ -102,15 +102,15 @@ public void test001_fileUploadTest() throws Exception { Assert.assertTrue(!termVectorService.getTermVector(Arrays.asList(upload.fileId), 10).isEmpty()); Assert.assertEquals( - "informatik", + "java", termVectorService.loadUnstemmedWordsOfDocument( - upload.fileId, "informat").get(0) + upload.fileId, "java").get(0) .getOriginalWord()); WriterMock writermock = ((WriterMock) upload.response.getWriter()); String json = writermock.getJson(); - Assert.assertEquals(String.format("{\"success\":true,\"newUuid\":\"%s\",\"uploadName\":\"IPB_Jahresbericht_2004.pdf\"}", upload.fileId), json); + Assert.assertEquals(String.format("{\"success\":true,\"newUuid\":\"%s\",\"uploadName\":\"Document1.pdf\"}", upload.fileId), json); } @@ -120,7 +120,7 @@ public void test002_fileUploadTestNoCollectionFound() throws Exception { fileObjectService, publicUser, new AsyncContextMock( - new File(exampleDocsRootFolder + "IPB_Jahresbericht_2004.pdf"), + new File(exampleDocsRootFolder + "Document1.pdf"), "test-coll-does-not-exist"), collectionService, "target/test-classes/collections"); @@ -133,25 +133,7 @@ public void test002_fileUploadTestNoCollectionFound() throws Exception { } @Test - public void test003_fileUploadWithSmallNumbers() throws Exception { - createAndSaveNewCol(); - UploadToColMock upload = new UploadToColMock( - fileObjectService, - publicUser, - new AsyncContextMock( - new File(exampleDocsRootFolder + "ShortNumberExample.docx"), - col.getName()), - collectionService, - "target/test-classes/collections"); - - upload.run(); - List terms = termVectorService.getTermVector(Arrays.asList(upload.fileId), 100); - Assert.assertEquals(4, terms.size()); - - } - - @Test - public void test004_regExForNumbers() { + public void test003_regExForNumbers() { String replacement = " "; String regEx = "(\\[|<||-|,| |^|\\(|\\{)\\d+([\\W])*\\d*( |$|\\)|,|\\}|-|\\.|\\%|\\]|>)"; From c071310f4e91bdd63c80f1debfe71f8e2066f08e Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Sun, 1 Oct 2023 13:28:37 +0200 Subject: [PATCH 14/28] fix ui test-compile --- .../ipb_halle/kx/service/QueryWebService.java | 57 ++++++++++++ ui/pom.xml | 9 ++ .../reporting/job/ReportJobServiceTest.java | 3 +- .../lbac/reporting/report/ReportMgrTest.java | 6 +- .../PostgresqlContainerExtension.java | 87 ------------------- 5 files changed, 71 insertions(+), 91 deletions(-) create mode 100644 kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java delete mode 100644 ui/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java new file mode 100644 index 000000000..94d85793e --- /dev/null +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java @@ -0,0 +1,57 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.annotation.Resource; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +@WebServlet(name = "QueryWebService", urlPatterns = {"/query/*"}, asyncSupported = true) +public class QueryWebService extends HttpServlet { + + private final static long serialVersionUID = 1L; + + private final Logger logger = LogManager.getLogger(QueryWebService.class); + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + logger.info("doPost(): request received."); + try { + final PrintWriter out = resp.getWriter(); + out.write(processRequest(req)); + } catch (IOException e) { + logger.error((Throwable) e); + } + } + + private String processRequest(HttpServletRequest req) { + try { + + } catch (Exception e) { + logger.warn((Throwable) e); + } + return "Error"; + } +} diff --git a/ui/pom.xml b/ui/pom.xml index 73806c139..b952d8f31 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -280,6 +280,15 @@ 1.0 + + + de.ipb-halle + crimsy-test + 1.0.0 + test + + + com.googlecode.owasp-java-html-sanitizer diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java index 42c09947c..4b7ccee98 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.reporting.job; +import de.ipb_halle.test.ManagedExecutorServiceMock; import static de.ipb_halle.lbac.device.job.JobService.CONDITION_JOBTYPE; import static de.ipb_halle.lbac.device.job.JobService.CONDITION_STATUS; import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; @@ -90,7 +91,7 @@ public static WebArchive createDeployment() { @BeforeEach private void before() { - managedExecutorService = new ManagedExecutorServiceMock(); + managedExecutorService = new ManagedExecutorServiceMock(2); reportJobService.setManagedExecutorService(managedExecutorService); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java index 9c0c3014c..0ffbe6655 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java @@ -45,7 +45,7 @@ import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.device.job.Job; import de.ipb_halle.lbac.device.job.JobService; -import de.ipb_halle.lbac.reporting.job.ManagedExecutorServiceMock; +import de.ipb_halle.test.ManagedExecutorServiceMock; import de.ipb_halle.lbac.reporting.job.ReportJobPojo; import de.ipb_halle.lbac.reporting.job.ReportJobService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -74,7 +74,7 @@ public static WebArchive createDeployment() { @BeforeEach private void before() { - managedExecutorService = new ManagedExecutorServiceMock(); + managedExecutorService = new ManagedExecutorServiceMock(2); reportJobService.setManagedExecutorService(managedExecutorService); } @@ -144,4 +144,4 @@ private ReportJobPojo deserializeToReportJobPojo(byte[] bytes) { } return (ReportJobPojo) o; } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java b/ui/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java deleted file mode 100644 index bf2224853..000000000 --- a/ui/src/test/java/de/ipb_halle/testcontainers/PostgresqlContainerExtension.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.testcontainers; - -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.containers.Container.ExecResult; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.utility.DockerImageName; -import org.testcontainers.utility.MountableFile; - -/** - * JUnit5 extension that starts a PostgreSQL docker container once - * before all tests are invoked. Testcontainer's singleton - * container pattern is employed, thus the container is teared down when the - * JVM shuts down. The docker container exposed its port 5432 to the host on - * port 65432 to avoid conflicts with existing PostgreSQL installations. - *

- * Usage: Annotate the test class with - * {@code @ExtendWith(PostgresqlContainerExtension.class)} - * - * @author flange - */ -public class PostgresqlContainerExtension implements BeforeAllCallback { - private static final String[] SCHEMA_FILES = { "00001.sql", "00002.sql", "00003.sql" }; - private static final String IMAGE_NAME = "ipbhalle/crimsydb:bingo_pg12"; - - private static final AtomicBoolean FIRST_RUN = new AtomicBoolean(true); - private PostgreSQLContainer container; - - private Logger logger = LogManager.getLogger(this.getClass().getName()); - - @Override - public void beforeAll(ExtensionContext context) throws Exception { - if (FIRST_RUN.getAndSet(false)) { - startContainer(); - } - } - - @SuppressWarnings("resource") - private void startContainer() throws Exception { - DockerImageName customPostgresImage = DockerImageName.parse(IMAGE_NAME).asCompatibleSubstituteFor("postgres"); - container = new PostgreSQLContainer<>(customPostgresImage).withUsername("postgres"); - container.setPortBindings(Arrays.asList("65432:5432")); - - logger.info("Starting Postgresql container with image " + customPostgresImage.toString()); - container.start(); - - for (String schemaFile : SCHEMA_FILES) { - copySchema("schema/" + schemaFile); - applySchema(schemaFile); - } - } - - private void copySchema(String filename) { - logger.info("Copy schema file " + filename + " into container path /"); - container.copyFileToContainer(MountableFile.forClasspathResource(filename), "/"); - } - - private void applySchema(String filename) throws Exception { - logger.info("Load schema file /" + filename); - ExecResult result = container.execInContainer("su", "postgres", "-c psql < /" + filename); - logger.info("Stdout: " + result.getStdout()); - logger.error("Stderr: " + result.getStderr()); - } -} \ No newline at end of file From ed18f4497cc9c4cca7c9777737510f01c9c0225a Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Sun, 1 Oct 2023 16:22:03 +0200 Subject: [PATCH 15/28] QueryWebService --- .../ipb_halle/kx/service/QueryWebService.java | 69 ++++++++++- .../service}/queryParserFilterDefinition.json | 0 .../kx/service/QueryWebServiceTest.java | 113 ++++++++++++++++++ .../kx/service/TextWebServiceTest.java | 4 +- .../lbac/search/SearchQueryStemmer.java | 29 +---- .../lbac/search/SearchQueryStemmerTest.java | 40 ------- 6 files changed, 183 insertions(+), 72 deletions(-) rename {ui/src/main/resources/de/ipb_halle/lbac/search => kx-web/src/main/resources/de/ipb_halle/kx/service}/queryParserFilterDefinition.json (100%) create mode 100644 kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java delete mode 100644 ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java index 94d85793e..aa843d75c 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java @@ -17,8 +17,18 @@ */ package de.ipb_halle.kx.service; +import de.ipb_halle.tx.text.ParseTool; +import de.ipb_halle.tx.text.TextRecord; +import de.ipb_halle.tx.text.properties.Language; +import de.ipb_halle.tx.text.properties.TextProperty; +import de.ipb_halle.tx.text.properties.Word; + +import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Resource; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -33,6 +43,8 @@ public class QueryWebService extends HttpServlet { private final static long serialVersionUID = 1L; + private final static String FILTER_DEFINITION = "queryParserFilterDefinition.json"; + private final Logger logger = LogManager.getLogger(QueryWebService.class); @Override @@ -47,11 +59,64 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) { } private String processRequest(HttpServletRequest req) { - try { - + StringBuilder query = new StringBuilder(); + try (BufferedReader reader = req.getReader()) { + String line = ""; + while ((line = reader.readLine()) != null) { + query.append(line); + query.append(" "); + } + return stemmQuery(query.toString()); } catch (Exception e) { logger.warn((Throwable) e); } return "Error"; } + + public String stemmQuery(String queryString) { + TextRecord textRecord = setupTextRecord(queryString); + textRecord = setupParseTool().parseSingleTextRecord(textRecord); + return getStemmedString(getSetOfWords(textRecord, queryString)); + } + + private String getStemmedString(Set words) { + StringBuilder sb = new StringBuilder(); + AtomicReference sep = new AtomicReference<> (""); + for (String word : words) { + sb.append(sep.getAndSet(" ")); + sb.append(word); + } + return sb.toString(); + } + + private Set getSetOfWords(TextRecord textRecord, String queryString) { + Set words = new HashSet<> (); + for (TextProperty prop : textRecord.getProperties(Word.TYPE)) { + Word w = (Word) prop; + String wStr = queryString.substring(w.getStart(), w.getEnd()); + if (wStr.trim().length() > 0) { + for (String stem : w.getStemSet()) { + words.add(stem); + } + } + } + return words; + } + + private TextRecord setupTextRecord(String queryString) { + TextRecord textRecord = new TextRecord(queryString); + int rank = 0; + for (String lang : new String[]{"en", "de", "fr", "es", "pt"}) { + textRecord.addProperty(new Language(0, queryString.length(), lang, rank)); + rank++; + } + return textRecord; + } + + private ParseTool setupParseTool() { + ParseTool parseTool = new ParseTool(); + parseTool.setFilterDefinition(getClass().getResourceAsStream(FILTER_DEFINITION)); + parseTool.initFilter(); + return parseTool; + } } diff --git a/ui/src/main/resources/de/ipb_halle/lbac/search/queryParserFilterDefinition.json b/kx-web/src/main/resources/de/ipb_halle/kx/service/queryParserFilterDefinition.json similarity index 100% rename from ui/src/main/resources/de/ipb_halle/lbac/search/queryParserFilterDefinition.json rename to kx-web/src/main/resources/de/ipb_halle/kx/service/queryParserFilterDefinition.json diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java new file mode 100644 index 000000000..1113ad936 --- /dev/null +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java @@ -0,0 +1,113 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.kx.service; + +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.inject.Inject; +import org.apache.http.HttpResponse; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.CloseableHttpClient; +//import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +/** + * + * @author fblocal + */ +@ExtendWith(PostgresqlContainerExtension.class) +@ExtendWith(ArquillianExtension.class) +public class QueryWebServiceTest { + + @ArquillianResource + URL baseURL; + + @Inject + private QueryWebService queryWebService; + + @Deployment + public static WebArchive createDeployment() { + System.setProperty("log4j.configurationFile", "log4j2-test.xml"); + + WebArchive archive = ShrinkWrap.create(WebArchive.class, "QueryWebServiceTest.war") + .addClass(QueryWebService.class) + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; + } + + @BeforeEach + public void init() { + } + + private String doRequest(String query) { + // port number must match the arquillian setting + try (CloseableHttpClient client = HttpClientBuilder.create().build()) {; + HttpPost post = new HttpPost( + new URL( + baseURL, "query").toExternalForm()); + + HttpEntity entity = new ByteArrayEntity(query.getBytes("UTF-8")); + post.setEntity(entity); + HttpResponse response = client.execute(post); + String result = EntityUtils.toString(response.getEntity()); + + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode(), "match HTTP status code"); + return result; + } catch (Exception e) { + assertTrue(false, "unexpected exception"); + } + return ""; + } + + @Test + @RunAsClient + public void test001_QueryWebService() throws IOException { + + Set expected = new HashSet<> (Arrays.asList("werkzeug", "gebrauch", "gebrauchen")); + Set actual = new HashSet<> (Arrays.asList(doRequest("Werkzeuge gebrauchen").trim().split(" "))); + assertEquals(expected, actual); + + } +} diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java index b9c023806..e54130051 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java @@ -32,10 +32,10 @@ import java.util.ArrayList; import javax.inject.Inject; import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.HttpClientBuilder; -import org.eclipse.jetty.http.HttpStatus; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.junit5.ArquillianExtension; @@ -125,7 +125,7 @@ private String doRequest(Integer fileId, TextWebRequestType type) throws IOExcep ).toExternalForm()); HttpResponse response = HttpClientBuilder.create().build().execute(request); - assertEquals(HttpStatus.OK_200, response.getStatusLine().getStatusCode(), "match HTTP status code"); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode(), "match HTTP status code"); return streamToString(response.getEntity().getContent()); } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java b/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java index ab619662b..0b60e7c8a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java @@ -27,34 +27,7 @@ */ public class SearchQueryStemmer { - protected String filterDefinition = "queryParserFilterDefinition.json"; - public Set stemmQuery(String queryString) { - return new HashSet (Arrays.asList("Not", "implemented", "yet")); + throw new RuntimeException("xxxxx ToDo: SearchQueryStemmer not implemented"); } - - - /* - public StemmedWordGroup stemmQuery(String queryString) { - StemmedWordGroup back = new StemmedWordGroup(); - TextRecord tr = new TextRecord(queryString); - int rank = 0; - for (String lang : new String[]{"en", "de", "fr", "es", "pt"}) { - tr.addProperty(new Language(0, queryString.length(), lang, rank)); - rank++; - } - ParseTool pt = new ParseTool(); - pt.setFilterDefinition(getClass().getResourceAsStream(filterDefinition)); - pt.initFilter(); - tr = pt.parseSingleTextRecord(tr); - for (TextProperty prop : tr.getProperties(Word.TYPE)) { - Word w = (Word) prop; - String wStr = queryString.substring(w.getStart(), w.getEnd()); - if (wStr.trim().length() > 0) { - back.addStemmedWord(wStr, w.getStemSet()); - } - } - return back; - } -*/ } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java deleted file mode 100644 index f6e63f29f..000000000 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchQueryStemmerTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.search; - -/** - * - * @author fmauz - */ -import java.util.Set; -import org.junit.Assert; -import org.junit.jupiter.api.Test; - -public class SearchQueryStemmerTest { - - - @Test - public void test001_stemmQuery() { - SearchQueryStemmer sqs=new SearchQueryStemmer(); - Set results=sqs.stemmQuery("Werkzeuge gebrauchen"); - Assert.assertTrue(results.contains("werkzeug")); - Assert.assertTrue(results.contains("gebrauch")); - Assert.assertEquals(sqs.stemmQuery("").size(), 0); - } - -} From 30ee5c24b2dc7b20d16f1a755cffca37ce556502 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Mon, 2 Oct 2023 11:07:35 +0200 Subject: [PATCH 16/28] fix some unit tests for ui --- .../kx/service/QueryWebServiceTest.java | 1 - .../lbac/search/SearchQueryStemmer.java | 41 +++++- .../document/DocumentSearchService.java | 8 +- .../lbac/search/wordcloud/WordCloudBean.java | 5 + .../ipb_halle/lbac/base/DocumentCreator.java | 130 ++++++++++++++---- .../document/DocumentSearchServiceTest.java | 5 + .../search/mocks/SearchQueryStemmerMock.java | 46 +++++++ .../search/wordcloud/WordCloudBeanTest.java | 4 + 8 files changed, 209 insertions(+), 31 deletions(-) create mode 100644 ui/src/test/java/de/ipb_halle/lbac/search/mocks/SearchQueryStemmerMock.java diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java index 1113ad936..c3ceee981 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/QueryWebServiceTest.java @@ -35,7 +35,6 @@ import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; -//import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; import org.jboss.arquillian.container.test.api.Deployment; diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java b/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java index 0b60e7c8a..77ce5ba0f 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/SearchQueryStemmer.java @@ -17,9 +17,24 @@ */ package de.ipb_halle.lbac.search; +import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import org.apache.http.HttpResponse; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + /** * @@ -27,7 +42,31 @@ */ public class SearchQueryStemmer { + private final String baseURL = "http://localhost:8080/kx-web/"; + private final Logger logger = LogManager.getLogger(SearchQueryStemmer.class.getName()); + public Set stemmQuery(String queryString) { - throw new RuntimeException("xxxxx ToDo: SearchQueryStemmer not implemented"); + return new HashSet<> (Arrays.asList(doRequest(queryString).trim().split(" "))); } + + private String doRequest(String query) { + try (CloseableHttpClient client = HttpClientBuilder.create().build()) {; + HttpPost post = new HttpPost( + new URL(new URL(baseURL), "query").toExternalForm()); + + HttpEntity entity = new ByteArrayEntity(query.getBytes("UTF-8")); + post.setEntity(entity); + HttpResponse response = client.execute(post); + String result = EntityUtils.toString(response.getEntity()); + + int httpStatus = response.getStatusLine().getStatusCode(); + if (httpStatus != HttpStatus.SC_OK) { + throw new Exception(String.format("Unexpected HTTP status: %d", httpStatus)); + } + return result; + } catch (Exception e) { + logger.warn("doRequest caught an exception: ", (Throwable) e); + } + return ""; + } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java index e61290aeb..b3b45526d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/document/DocumentSearchService.java @@ -84,7 +84,7 @@ public class DocumentSearchService { private boolean development = false; private String uriOfPublicColl; - protected SearchQueryStemmer searchQueryStemmer; + private SearchQueryStemmer searchQueryStemmer = new SearchQueryStemmer(); private DocumentEntityGraphBuilder graphBuilder; @PostConstruct @@ -128,7 +128,6 @@ public DocumentSearchState actionStartDocumentSearch( this.uriOfPublicColl = uriOfPublicColl; searchState.clearState(); - searchQueryStemmer = new SearchQueryStemmer(); // fetches all documents of the collection and adds the total // number of documents in the collection to the search state Set normalizedTerms = searchQueryStemmer.stemmQuery(searchText); @@ -354,4 +353,9 @@ private EntityGraph createEntityGraph() { graphBuilder = new DocumentEntityGraphBuilder(); return graphBuilder.buildEntityGraph(true); } + + // for test purposes + public void setSearchQueryStemmer(SearchQueryStemmer stemmer) { + searchQueryStemmer = stemmer; + } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java index 1b839750e..a19219176 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBean.java @@ -430,4 +430,9 @@ public void setTagList(Set tagList) { public List getTagsAsList() { return new ArrayList<>(tagList); } + + // for injecting of mock objects during test + protected DocumentSearchService getDocumentSearchService() { + return searchService; + } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java b/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java index 91426d7ca..9c43031bf 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java @@ -17,7 +17,10 @@ */ package de.ipb_halle.lbac.base; +import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.termvector.StemmedWordOrigin; +import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.kx.termvector.TermVectorService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; @@ -29,6 +32,10 @@ import java.io.File; import java.io.FileNotFoundException; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.openejb.loader.Files; /** @@ -37,7 +44,7 @@ */ public class DocumentCreator { - protected Collection col; + protected Collection collection; protected AsyncContextMock asynContext; protected String exampleDocsRootFolder = "target/test-classes/exampledocs/"; @@ -47,6 +54,9 @@ public class DocumentCreator { protected TermVectorService termVectorService; protected User user; + private Map> termVectorMap; + private Map> stemmedWordsMap; + public DocumentCreator( FileObjectService fileObjectService, CollectionService collectionService, @@ -57,6 +67,8 @@ public DocumentCreator( this.collectionService = collectionService; this.nodeService = nodeService; this.termVectorService = termVectorService; + setupTermVectorMap(); + setupStemmedWordsMap(); } public Collection uploadDocuments(User user, String collectionName, String... files) throws FileNotFoundException, InterruptedException { @@ -64,38 +76,102 @@ public Collection uploadDocuments(User user, String collectionName, String... fi this.user = user; createAndSaveNewCol(collectionName); for (String file : files) { - uploadDocument(file); + setupDocument(file); } - return col; + return collection; } private void createAndSaveNewCol(String colName) { - col = new Collection(); - col.setACList(GlobalAdmissionContext.getPublicReadACL()); - col.setDescription("xxx"); - col.setIndexPath("/"); - col.setName(colName); - col.setNode(nodeService.getLocalNode()); - col.setOwner(user); - col.setStoragePath("/"); - col = collectionService.save(col); - col.COLLECTIONS_BASE_FOLDER = "target/test-classes/collections"; + collection = new Collection(); + collection.setACList(GlobalAdmissionContext.getPublicReadACL()); + collection.setDescription("xxx"); + collection.setIndexPath("/"); + collection.setName(colName); + collection.setNode(nodeService.getLocalNode()); + collection.setOwner(user); + collection.setStoragePath("/"); + collection = collectionService.save(collection); + collection.COLLECTIONS_BASE_FOLDER = "target/test-classes/collections"; + } + + private void setupDocument(String name) { + FileObject fileObj = new FileObject(); + fileObj.setName(name); + fileObj.setCollectionId(collection.getId()); + fileObj.setFileLocation("dummy/location/" + name); + fileObj = fileObjectService.save(fileObj); + + saveTermVectors(name, fileObj.getId()); + termVectorService.saveUnstemmedWordsOfDocument(stemmedWordsMap.get(name), fileObj.getId()); } - private void uploadDocument(String documentName) throws FileNotFoundException, InterruptedException { - asynContext = new AsyncContextMock( - new File(exampleDocsRootFolder + documentName), - col.getName()); - UploadToColMock upload = new UploadToColMock( - fileObjectService, - user, - asynContext, - collectionService, - "target/test-classes/collections"); - - upload.run(); - while (!asynContext.isComplete()) { - Thread.sleep(500); + private void saveTermVectors(String name, Integer fileId) { + List tvl = termVectorMap.get(name); + for (TermVector tv : tvl) { + tv.setFileId(fileId); } + termVectorService.saveTermVectors(tvl); + } + + private void setupTermVectorMap() { + termVectorMap = new HashMap<> (); + termVectorMap.put("Document1.pdf", Arrays.asList( + // root, file, freq + new TermVector("java", 0, 3), + new TermVector("failure", 0, 37))); + + termVectorMap.put("Document2.pdf", Arrays.asList( + new TermVector("java", 0, 2), + new TermVector("failure", 0, 98))); + + termVectorMap.put("Document3.pdf", Arrays.asList( + new TermVector("failure", 0, 25))); + + termVectorMap.put("Document18.pdf", Arrays.asList( + new TermVector("check", 0, 1), + new TermVector("java", 0, 1), + new TermVector("stem", 0, 1), + new TermVector("tini", 0, 1), + new TermVector("word", 0, 1))); + + termVectorMap.put("Wasserstoff.docx", Arrays.asList( + new TermVector("dokumentsuch", 0, 1), + new TermVector("vorschritt", 0, 1), + new TermVector("gefund", 0, 1), + new TermVector("material", 0, 1), + new TermVector("durchzufuhr", 0, 1), + new TermVector("wasserstoff", 0, 1), + new TermVector("testdokument", 0, 1))); + } + + private void setupStemmedWordsMap() { + stemmedWordsMap = new HashMap<> (); + + stemmedWordsMap.put("Document1.pdf", Arrays.asList( + new StemmedWordOrigin("java", "java"), + new StemmedWordOrigin("failure", "failure"))); + + stemmedWordsMap.put("Document2.pdf", Arrays.asList( + new StemmedWordOrigin("java", "java"), + new StemmedWordOrigin("failure", "failure"))); + + stemmedWordsMap.put("Document3.pdf", Arrays.asList( + new StemmedWordOrigin("failure", "failure"))); + + stemmedWordsMap.put("Document18.pdf", Arrays.asList( + new StemmedWordOrigin("check", "check"), + new StemmedWordOrigin("java", "java"), + new StemmedWordOrigin("stem", "stemming"), + new StemmedWordOrigin("tini", "tiny"), + new StemmedWordOrigin("word", "word"))); + + stemmedWordsMap.put("Wasserstoff.docx", Arrays.asList( + new StemmedWordOrigin("dokumentsuch", "dokumentsuche"), + new StemmedWordOrigin("vorschritt", "vorschritt"), + new StemmedWordOrigin("gefund", "gefunden"), + new StemmedWordOrigin("material", "material"), + new StemmedWordOrigin("durchzufuhr", "durchzuführen"), + new StemmedWordOrigin("wasserstoff", "wasserstoff"), + new StemmedWordOrigin("testdokument", "testdokument"))); } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java index 555077f22..04c1c431d 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/document/DocumentSearchServiceTest.java @@ -38,6 +38,7 @@ import de.ipb_halle.lbac.search.NetObject; import de.ipb_halle.lbac.search.SearchRequest; import de.ipb_halle.lbac.search.SearchResult; +import de.ipb_halle.lbac.search.mocks.SearchQueryStemmerMock; import de.ipb_halle.lbac.search.relevance.RelevanceCalculator; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -133,6 +134,8 @@ public void test002_loadDocuments_withOneWordRoot() throws FileNotFoundException builder.setWordRoots(new HashSet<>(Arrays.asList("java"))); SearchRequest request = builder.build(); + documentSearchService.setSearchQueryStemmer( + new SearchQueryStemmerMock("java")); SearchResult result = documentSearchService.loadDocuments(request); List netObjects = result.getAllFoundObjects(Document.class); List documents = new ArrayList<>(); @@ -155,6 +158,8 @@ public void test003_loadDocuments_withTwoWordRoot() throws FileNotFoundException builder.setWordRoots(new HashSet<>(Arrays.asList("java", "failure"))); SearchRequest request = builder.build(); Object o=entityManagerService.doSqlQuery("SELECT id,name,collection_id from files"); + documentSearchService.setSearchQueryStemmer( + new SearchQueryStemmerMock("java failure")); SearchResult result = documentSearchService.loadDocuments(request); List netObjects = result.getAllFoundObjects(Document.class); Assert.assertEquals(3, netObjects.size()); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/mocks/SearchQueryStemmerMock.java b/ui/src/test/java/de/ipb_halle/lbac/search/mocks/SearchQueryStemmerMock.java new file mode 100644 index 000000000..308c0c9d1 --- /dev/null +++ b/ui/src/test/java/de/ipb_halle/lbac/search/mocks/SearchQueryStemmerMock.java @@ -0,0 +1,46 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.search.mocks; + +import de.ipb_halle.lbac.search.SearchQueryStemmer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * + * @author fbroda + */ +public class SearchQueryStemmerMock extends SearchQueryStemmer { + + private Set stemmedQuery; + + public SearchQueryStemmerMock(String query) { + setStem(query); + } + + public void setStem(String query) { + stemmedQuery = new HashSet (Arrays.asList(query.trim().split(" "))); + } + + @Override + public Set stemmQuery(String queryString) { + // ignore input, return pre-set result + return stemmedQuery; + } +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java index 2534a88e3..a2e8aa70f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/wordcloud/WordCloudBeanTest.java @@ -25,6 +25,7 @@ import de.ipb_halle.lbac.admission.ACListService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.collections.CollectionService; +import de.ipb_halle.lbac.search.mocks.SearchQueryStemmerMock; import de.ipb_halle.lbac.service.CloudService; import de.ipb_halle.lbac.service.CloudNodeService; import de.ipb_halle.lbac.service.FileService; @@ -111,6 +112,7 @@ public void before() throws IOException { @Test public void test001_startSearch() { + wordCloudBean.getDocumentSearchService().setSearchQueryStemmer(new SearchQueryStemmerMock("java")); wordCloudBean.setSearchTermInput("Java"); wordCloudBean.startSearch(); @@ -122,6 +124,7 @@ public void test001_startSearch() { @Test public void test002_clearCloudState() { + wordCloudBean.getDocumentSearchService().setSearchQueryStemmer(new SearchQueryStemmerMock("java")); wordCloudBean.setSearchTermInput("Java"); wordCloudBean.startSearch(); @@ -142,6 +145,7 @@ public void test002_clearCloudState() { @Test public void test003_selectWordInCloud() { + wordCloudBean.getDocumentSearchService().setSearchQueryStemmer(new SearchQueryStemmerMock("java")); wordCloudBean.setSearchTermInput("Java"); wordCloudBean.startSearch(); From 39d508ea6c8ad5d60ed43cc7c42a0bcde6acd458 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Mon, 2 Oct 2023 11:43:27 +0200 Subject: [PATCH 17/28] fix SearchServiceTest and SearchBeanTest --- ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java | 4 ++++ .../java/de/ipb_halle/lbac/search/SearchServiceTest.java | 5 +++++ .../java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java | 2 ++ 3 files changed, 11 insertions(+) diff --git a/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java b/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java index 45722a61e..a3cee48cc 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/search/SearchService.java @@ -175,4 +175,8 @@ private void sortSearchRequestsByPrio(List requests) { } } + // for testing purposes + public void setSearchQueryStemmer(SearchQueryStemmer stemmer) { + searchQueryStemmer = stemmer; + } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java index a50437f4a..82c4d48a7 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/SearchServiceTest.java @@ -71,6 +71,7 @@ import de.ipb_halle.lbac.project.ProjectService; import de.ipb_halle.lbac.search.document.DocumentSearchRequestBuilder; import de.ipb_halle.lbac.search.document.DocumentSearchService; +import de.ipb_halle.lbac.search.mocks.SearchQueryStemmerMock; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.FileNotFoundException; import java.nio.file.Paths; @@ -443,12 +444,16 @@ public void test010_searchWithAugmentedDocumentRequest() { DocumentSearchRequestBuilder docRequestBuilder = new DocumentSearchRequestBuilder(publicUser, 0, 25); docRequestBuilder.setWordRoot("x"); + // provide stemmer with material names + searchService.setSearchQueryStemmer(new SearchQueryStemmerMock("wasserstoff h")); SearchResult result = searchService.search( Arrays.asList(docRequestBuilder.build(), matRequestbuilder.build()), localNode); Assert.assertEquals(2, result.getAllFoundObjects().size()); + // now provide stemmer with 'original' search term + searchService.setSearchQueryStemmer(new SearchQueryStemmerMock("x")); result = searchService.search( Arrays.asList(docRequestBuilder.build()), localNode); diff --git a/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java index bdd0a0a4d..abfee5c27 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/search/bean/SearchBeanTest.java @@ -47,6 +47,7 @@ import de.ipb_halle.lbac.search.SearchService; import de.ipb_halle.lbac.search.SearchWebClient; import de.ipb_halle.lbac.search.document.DocumentSearchService; +import de.ipb_halle.lbac.search.mocks.SearchQueryStemmerMock; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.FileNotFoundException; import java.util.Arrays; @@ -150,6 +151,7 @@ public void test001_actionAddFoundObjectsToShownObjects() { @Test public void test002_actionTriggerSearch() { + searchService.setSearchQueryStemmer(new SearchQueryStemmerMock("java")); SearchBean bean = new SearchBean(searchService, publicUser, nodeService); bean.getSearchFilter().setSearchTerms("java"); bean.setOrchestrator(orchestrator); From bd4fd046a8be67e3a2ed28d36f9662fad3c5b75d Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Mon, 2 Oct 2023 14:28:18 +0200 Subject: [PATCH 18/28] Separate Text Extraction from UI (WORK IN PROGRESS) - fixed unit tests - known error: kx.war cannot be deployed --- docker/ui/Dockerfile | 1 + docker/ui/tomee.xml | 42 +++++----- kx-web/src/main/resources/log4j2.xml | 2 +- .../src/main/webapp/WEB-INF/persistence.xml | 50 ++++++++++++ kx-web/src/main/webapp/WEB-INF/web.xml | 5 +- .../de/ipb_halle/lbac/file/AnalyseClient.java | 76 +++++++++++++++++++ .../de/ipb_halle/lbac/file/UploadToCol.java | 15 +++- .../ipb_halle/lbac/base/DocumentCreator.java | 9 ++- .../ipb_halle/lbac/file/UploadToColTest.java | 19 ++++- .../lbac/file/mock/AnalyseClientMock.java | 45 +++++++++++ util/bin/buildDocker.sh | 1 + 11 files changed, 235 insertions(+), 30 deletions(-) create mode 100644 kx-web/src/main/webapp/WEB-INF/persistence.xml create mode 100644 ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java create mode 100644 ui/src/test/java/de/ipb_halle/lbac/file/mock/AnalyseClientMock.java diff --git a/docker/ui/Dockerfile b/docker/ui/Dockerfile index 0c462a748..f33f7efdc 100644 --- a/docker/ui/Dockerfile +++ b/docker/ui/Dockerfile @@ -27,6 +27,7 @@ COPY setup.sh / # COPY extralib /usr/local/tomee/extralib COPY ui.war /usr/local/tomee/webapps/ +COPY kx-web.war /usr/local/tomee/webapps/ COPY logpurge.sh /usr/local/bin/ COPY tomcat-users.xml /usr/local/tomee/conf/ COPY tomee.xml /usr/local/tomee/conf/ diff --git a/docker/ui/tomee.xml b/docker/ui/tomee.xml index 5e3379511..bfad57066 100644 --- a/docker/ui/tomee.xml +++ b/docker/ui/tomee.xml @@ -1,22 +1,22 @@ @@ -57,9 +57,15 @@ Queue = 50 - Core = 4 + Core = 2 Max = 4 KeepAlive = 5 s Queue = 10 + + Core = 2 + Max = 4 + KeepAlive = 5 s + Queue = 20 + diff --git a/kx-web/src/main/resources/log4j2.xml b/kx-web/src/main/resources/log4j2.xml index 692761c6b..09222cdd4 100644 --- a/kx-web/src/main/resources/log4j2.xml +++ b/kx-web/src/main/resources/log4j2.xml @@ -13,7 +13,7 @@ diff --git a/kx-web/src/main/webapp/WEB-INF/persistence.xml b/kx-web/src/main/webapp/WEB-INF/persistence.xml new file mode 100644 index 000000000..4e4b78073 --- /dev/null +++ b/kx-web/src/main/webapp/WEB-INF/persistence.xml @@ -0,0 +1,50 @@ + + + + + + CRIMSy kx + + org.hibernate.jpa.HibernatePersistenceProvider + uiDS + + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity + true + + + + + + + + + + + + + + + diff --git a/kx-web/src/main/webapp/WEB-INF/web.xml b/kx-web/src/main/webapp/WEB-INF/web.xml index 94dc00359..3fc5938ed 100644 --- a/kx-web/src/main/webapp/WEB-INF/web.xml +++ b/kx-web/src/main/webapp/WEB-INF/web.xml @@ -32,7 +32,4 @@ /process/* - - - - + diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java b/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java new file mode 100644 index 000000000..6b94b4be2 --- /dev/null +++ b/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java @@ -0,0 +1,76 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.file; + +import de.ipb_halle.kx.service.TextWebRequestType; +import de.ipb_halle.kx.service.TextWebStatus; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.HttpClientBuilder; + + +public class AnalyseClient { + + private final String baseURL = "http://localhost:8080/kx/"; + + /** + * make a GET request to the remote knowledge extractor + * endpoint. + * + * @param fileId id of the fileObject to be inspected + * @param type the request type: initially SUBMIT; QUERY thereafter + * @return ideally BUSY or DONE; can also return error codes + * + * @throws Exception if either an exception occurs during the request + * process or if the result cannot be converted to TextWebStatus. + */ + public TextWebStatus analyseFile(Integer fileId, TextWebRequestType type) throws Exception { + HttpUriRequest request = new HttpGet( + new URL( + new URL(baseURL), + String.format("process?fileId=%d&type=%s", fileId, type.toString()) + ).toExternalForm()); + HttpResponse response = HttpClientBuilder.create().build().execute(request); + + int httpStatus = response.getStatusLine().getStatusCode(); + if (httpStatus != HttpStatus.SC_OK) { + throw new Exception(String.format("Unexpected HTTP status: %d", httpStatus)); + } + return TextWebStatus.valueOf(streamToString(response.getEntity().getContent())); + } + + private String streamToString(InputStream inputStream) { + try (ByteArrayOutputStream result = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + for (int length; (length = inputStream.read(buffer)) != -1; ) { + result.write(buffer, 0, length); + } + // StandardCharsets.UTF_8.name() > JDK 7 + return result.toString("UTF-8"); + } catch (IOException e) { + // ignore + } + return ""; + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java index a57147db3..401974c7f 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/UploadToCol.java @@ -19,6 +19,8 @@ import de.ipb_halle.kx.file.AttachmentHolder; import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.service.TextWebRequestType; +import de.ipb_halle.kx.service.TextWebStatus; import de.ipb_halle.kx.termvector.StemmedWordOrigin; import de.ipb_halle.kx.termvector.TermVector; import de.ipb_halle.kx.termvector.TermVectorService; @@ -59,6 +61,7 @@ public class UploadToCol implements Runnable { protected FileSaver fileSaver; protected Integer fileId; protected FileObjectService fileObjectService; + protected AnalyseClient analyseClient = new AnalyseClient(); private final Logger logger; public UploadToCol( @@ -119,10 +122,16 @@ public void run() { request.getPart(HTTP_PART_FILENAME).getInputStream()); - throw new RuntimeException("xxxxx Need to do async call to KX-Web service"); + TextWebStatus status = analyseClient.analyseFile(fileId, TextWebRequestType.SUBMIT); + while (status == TextWebStatus.BUSY) { + Thread.sleep(400); + status = analyseClient.analyseFile(fileId, TextWebRequestType.QUERY); + } + if (status != TextWebStatus.DONE) { + throw new Exception("Analysis returned an unexpected status code: " + status.toString()); + } + response.getWriter().write(createJsonSuccessResponse(fileId, getFileNameFromRequest())); - -// response.getWriter().write(createJsonSuccessResponse(fileId, getFileNameFromRequest())); } catch (Exception e) { writeErrorMessage(e); logger.error(ExceptionUtils.getStackTrace(e)); diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java b/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java index 9c43031bf..7f030188b 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/DocumentCreator.java @@ -102,15 +102,20 @@ private void setupDocument(String name) { fileObj = fileObjectService.save(fileObj); saveTermVectors(name, fileObj.getId()); - termVectorService.saveUnstemmedWordsOfDocument(stemmedWordsMap.get(name), fileObj.getId()); } - private void saveTermVectors(String name, Integer fileId) { + /** + * mock TermVector and unstemmed words for a given file + * @param name document file name + * @param fileId id of FileObject + */ + public void saveTermVectors(String name, Integer fileId) { List tvl = termVectorMap.get(name); for (TermVector tv : tvl) { tv.setFileId(fileId); } termVectorService.saveTermVectors(tvl); + termVectorService.saveUnstemmedWordsOfDocument(stemmedWordsMap.get(name), fileId); } private void setupTermVectorMap() { diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java index d1a143259..6a5407a23 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/file/UploadToColTest.java @@ -19,13 +19,16 @@ import de.ipb_halle.kx.file.FileObject; import de.ipb_halle.kx.file.FileObjectService; +import de.ipb_halle.kx.service.TextWebStatus; import de.ipb_halle.kx.termvector.TermFrequency; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; +import de.ipb_halle.lbac.base.DocumentCreator; import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.collections.CollectionService; +import de.ipb_halle.lbac.file.mock.AnalyseClientMock; import de.ipb_halle.lbac.file.mock.AsyncContextMock; import de.ipb_halle.lbac.file.mock.HttpServletResponseMock.WriterMock; import de.ipb_halle.lbac.file.mock.UploadToColMock; @@ -65,10 +68,17 @@ public class UploadToColTest extends TestBase { protected User publicUser; protected Collection col; + private DocumentCreator documentCreator; + @BeforeEach public void init() { Files.delete(Paths.get("target/test-classes/collections").toFile()); publicUser = memberService.loadUserById(GlobalAdmissionContext.PUBLIC_ACCOUNT_ID); + documentCreator = new DocumentCreator( + fileObjectService, + collectionService, + nodeService, // nodeService from TestBase + termVectorService); // termVectorService from TestBase } @AfterEach @@ -83,20 +93,25 @@ public void cleanUp() { @Test public void test001_fileUploadTest() throws Exception { + String docName = "Document1.pdf"; createAndSaveNewCol(); UploadToColMock upload = new UploadToColMock( fileObjectService, publicUser, new AsyncContextMock( - new File(exampleDocsRootFolder + "Document1.pdf"), + new File(exampleDocsRootFolder + docName), col.getName()), collectionService, "target/test-classes/collections"); + upload.analyseClient = new AnalyseClientMock( + Arrays.asList(TextWebStatus.BUSY, TextWebStatus.DONE)); upload.run(); + // mock termvector and unstemmed words + documentCreator.saveTermVectors(docName, upload.fileId); + Map cmap = new HashMap<>(); cmap.put("id", upload.fileId); - FileObject file = fileObjectService.load(cmap).get(0); Assert.assertNotNull(file); diff --git a/ui/src/test/java/de/ipb_halle/lbac/file/mock/AnalyseClientMock.java b/ui/src/test/java/de/ipb_halle/lbac/file/mock/AnalyseClientMock.java new file mode 100644 index 000000000..bfdbc32c7 --- /dev/null +++ b/ui/src/test/java/de/ipb_halle/lbac/file/mock/AnalyseClientMock.java @@ -0,0 +1,45 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.file.mock; + +import de.ipb_halle.kx.service.TextWebRequestType; +import de.ipb_halle.kx.service.TextWebStatus; +import de.ipb_halle.lbac.file.AnalyseClient; +import java.util.List; +import java.util.ListIterator; + +/** + * + * @author fbroda + */ +public class AnalyseClientMock extends AnalyseClient { + + private ListIterator statusListIterator; + + public AnalyseClientMock(List l) { + statusListIterator = l.listIterator(); + } + + @Override + public TextWebStatus analyseFile(Integer fileId, TextWebRequestType type) throws Exception { + if (statusListIterator.hasNext()) { + return statusListIterator.next(); + } + return TextWebStatus.PROCESSING_ERROR; + } +} diff --git a/util/bin/buildDocker.sh b/util/bin/buildDocker.sh index fe8ec2c08..c26d379dd 100755 --- a/util/bin/buildDocker.sh +++ b/util/bin/buildDocker.sh @@ -41,6 +41,7 @@ function compile { cp -r docker target/ cp -r target/extralib target/docker/ui/ cp ui/target/ui.war target/docker/ui/ + cp kx-web/target/kx.war target/docker/ui/ if [ -n "$STAGE_LABEL" ] ; then flags="$flags,$STAGE_LABEL" From be8645b611c4df9cbf155ba9826ab59a77294259 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Wed, 4 Oct 2023 14:24:39 +0200 Subject: [PATCH 19/28] fix previously undetected issues --- docker/ui/setup.sh | 11 ++++-- kx-web/pom.xml | 37 ++----------------- .../ipb_halle/kx/service/QueryWebService.java | 2 +- .../ipb_halle/kx/service/TextWebService.java | 4 +- kx-web/src/main/webapp/WEB-INF/beans.xml | 26 ++----------- .../src/main/webapp/WEB-INF/persistence.xml | 2 +- kx-web/src/main/webapp/WEB-INF/web.xml | 35 ------------------ .../kx/service/TextWebServiceTest.java | 15 +++++--- .../de/ipb_halle/lbac/file/AnalyseClient.java | 2 +- util/bin/buildDocker.sh | 2 +- 10 files changed, 32 insertions(+), 104 deletions(-) delete mode 100644 kx-web/src/main/webapp/WEB-INF/web.xml diff --git a/docker/ui/setup.sh b/docker/ui/setup.sh index 5d500959d..4166a3d82 100644 --- a/docker/ui/setup.sh +++ b/docker/ui/setup.sh @@ -51,11 +51,16 @@ adduser --no-create-home --gecos TomEE --home /usr/local/tomee \ # cat <> $TOMCAT_HOME/conf/system.properties # -# Disable CDI for Hibernate JPA -# (http://http://tomee-openejb.979440.n4.nabble.com/CDI-issues-tomee-7-0-2-td4680584.html) +# CRIMSy Settings +# - set path for file upload +# - define deploymentId format (otherwise ...) +# (https://stackoverflow.com/questions/4265762/tomcat-application-cannot-be-deployed-as-it-contains-deployment-ids-error) +# - disable CDI for Hibernate JPA +# (http://http://tomee-openejb.979440.n4.nabble.com/CDI-issues-tomee-7-0-2-td4680584.html) # -tomee.jpa.cdi = false de.ipb_halle.lbac.cloud.servlet.FileUploadExec.Path = /data/ui +openejb.deploymentId.format={moduleId}/{ejbName}" +tomee.jpa.cdi = false EOF chown -R tomee:tomee $TOMCAT_HOME diff --git a/kx-web/pom.xml b/kx-web/pom.xml index 9940162e8..3f96c0b9b 100644 --- a/kx-web/pom.xml +++ b/kx-web/pom.xml @@ -111,44 +111,14 @@ - - - maven-war-plugin - 2.4 + 3.3.2 - true - kx + false + kx-web true @@ -294,6 +264,7 @@ de.ipb-halle crimsy-test 1.0.0 + test diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java index aa843d75c..484b8c01c 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/QueryWebService.java @@ -38,7 +38,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -@WebServlet(name = "QueryWebService", urlPatterns = {"/query/*"}, asyncSupported = true) +@WebServlet(urlPatterns = {"/query"}) public class QueryWebService extends HttpServlet { private final static long serialVersionUID = 1L; diff --git a/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java index 648c07906..f7904bf82 100644 --- a/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java +++ b/kx-web/src/main/java/de/ipb_halle/kx/service/TextWebService.java @@ -34,7 +34,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -@WebServlet(name = "TextWebService", urlPatterns = {"/process/*"}, asyncSupported = true) +@WebServlet(urlPatterns = {"/process"}, asyncSupported = true) public class TextWebService extends HttpServlet { private final static long serialVersionUID = 1L; @@ -125,10 +125,10 @@ private TextWebStatus processQueryRequest(Integer fileId) { private void saveResults(IFileAnalyser analyser) { saveLanguage(analyser); + termVectorService.saveTermVectors(analyser.getTermVector()); termVectorService.saveUnstemmedWordsOfDocument( analyser.getWordOrigins(), analyser.getFileObject().getId()); - termVectorService.saveTermVectors(analyser.getTermVector()); } private void saveLanguage(IFileAnalyser analyser) { diff --git a/kx-web/src/main/webapp/WEB-INF/beans.xml b/kx-web/src/main/webapp/WEB-INF/beans.xml index dfd60e317..ff45d1767 100644 --- a/kx-web/src/main/webapp/WEB-INF/beans.xml +++ b/kx-web/src/main/webapp/WEB-INF/beans.xml @@ -1,24 +1,6 @@ - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" + bean-discovery-mode="annotated"> + \ No newline at end of file diff --git a/kx-web/src/main/webapp/WEB-INF/persistence.xml b/kx-web/src/main/webapp/WEB-INF/persistence.xml index 4e4b78073..be2f9c06c 100644 --- a/kx-web/src/main/webapp/WEB-INF/persistence.xml +++ b/kx-web/src/main/webapp/WEB-INF/persistence.xml @@ -36,7 +36,7 @@ - + - - CRIMSy web service for text / document processing and knowledge extraction - Knowledge Extractor - - - Text Processing Service - TextWebService - de.ipb_halle.kx.service.TextWebService - 1 - - - TextWebService - /process/* - - - diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java index e54130051..b986e57af 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/TextWebServiceTest.java @@ -29,7 +29,7 @@ import java.io.InputStream; import java.io.StringReader; import java.net.URL; -import java.util.ArrayList; +import java.util.Arrays; import javax.inject.Inject; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; @@ -146,10 +146,15 @@ public void test001_TextWebService() throws IOException { FileAnalyserMock mock = (FileAnalyserMock) jobTracker.getJob(fo.getId()); mock.setStatus(TextWebStatus.DONE); - mock.setLanguage("en"); - mock.setTermVectors(new ArrayList ()); - mock.setWordOrigins(new ArrayList ()); - + mock.setLanguage("de"); + mock.setTermVectors(Arrays.asList( + // root, file, freq + new TermVector ("kapsel", fo.getId(), 1), + new TermVector ("gefund", fo.getId(), 2))); + mock.setWordOrigins(Arrays.asList( + // stem, origin + new StemmedWordOrigin ("kapsel", "kapselung"), + new StemmedWordOrigin ("gefund", "gefunden"))); result = doRequest(fo.getId(), TextWebRequestType.QUERY); assertEquals(TextWebStatus.DONE.toString(), result, "successful job completion"); diff --git a/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java b/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java index 6b94b4be2..ad2002a5f 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java +++ b/ui/src/main/java/de/ipb_halle/lbac/file/AnalyseClient.java @@ -32,7 +32,7 @@ public class AnalyseClient { - private final String baseURL = "http://localhost:8080/kx/"; + private final String baseURL = "http://localhost:8080/kx-web/"; /** * make a GET request to the remote knowledge extractor diff --git a/util/bin/buildDocker.sh b/util/bin/buildDocker.sh index c26d379dd..ddea344df 100755 --- a/util/bin/buildDocker.sh +++ b/util/bin/buildDocker.sh @@ -41,7 +41,7 @@ function compile { cp -r docker target/ cp -r target/extralib target/docker/ui/ cp ui/target/ui.war target/docker/ui/ - cp kx-web/target/kx.war target/docker/ui/ + cp kx-web/target/kx-web.war target/docker/ui/ if [ -n "$STAGE_LABEL" ] ; then flags="$flags,$STAGE_LABEL" From 70f20f105ca8a22904a1feb61c6f61383a30d905 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Wed, 4 Oct 2023 16:37:36 +0200 Subject: [PATCH 20/28] Start separation of reporting from UI --- .../ipb_halle/test/EntityManagerService.java | 11 + kx-api/pom.xml | 14 - kx-api/src/test/resources/arquillian.xml | 2 +- pom.xml | 1 + reporting-api/pom.xml | 302 ++++++++++++++++++ .../ipb_halle}/reporting/report/Report.java | 4 +- .../reporting/report/ReportEntity.java | 4 +- .../reporting/report/ReportService.java | 2 +- .../reporting/report/ReportType.java | 4 +- .../reporting/report/ReportServiceTest.java | 29 +- .../reporting/report/ReportTest.java | 6 +- .../resources/PostgresqlContainerSchemaFiles | 5 + .../src/test/resources/arquillian.xml | 49 +++ .../src/test/resources/schema/00001.sql | 100 ++++++ .../src/test/resources/test-persistence.xml | 54 ++++ ui/pom.xml | 8 +- .../lbac/items/bean/ItemOverviewBean.java | 4 +- .../common/bean/MaterialOverviewBean.java | 4 +- .../lbac/reporting/job/ReportJobPojo.java | 4 +- .../lbac/reporting/report/ReportMgr.java | 3 + .../ipb_halle/lbac/items/ItemDeployment.java | 2 +- .../lbac/material/MaterialDeployment.java | 2 +- .../reporting/job/ReportJobServiceTest.java | 5 +- .../lbac/reporting/job/ReportTaskTest.java | 4 +- .../lbac/reporting/report/ReportMgrTest.java | 6 +- ui/src/test/resources/test-persistence.xml | 2 +- 26 files changed, 585 insertions(+), 46 deletions(-) create mode 100644 reporting-api/pom.xml rename {ui/src/main/java/de/ipb_halle/lbac => reporting-api/src/main/java/de/ipb_halle}/reporting/report/Report.java (93%) rename {ui/src/main/java/de/ipb_halle/lbac => reporting-api/src/main/java/de/ipb_halle}/reporting/report/ReportEntity.java (96%) rename {ui/src/main/java/de/ipb_halle/lbac => reporting-api/src/main/java/de/ipb_halle}/reporting/report/ReportService.java (97%) rename {ui/src/main/java/de/ipb_halle/lbac => reporting-api/src/main/java/de/ipb_halle}/reporting/report/ReportType.java (95%) rename {ui/src/test/java/de/ipb_halle/lbac => reporting-api/src/test/java/de/ipb_halle}/reporting/report/ReportServiceTest.java (65%) rename {ui/src/test/java/de/ipb_halle/lbac => reporting-api/src/test/java/de/ipb_halle}/reporting/report/ReportTest.java (94%) create mode 100644 reporting-api/src/test/resources/PostgresqlContainerSchemaFiles create mode 100644 reporting-api/src/test/resources/arquillian.xml create mode 100644 reporting-api/src/test/resources/schema/00001.sql create mode 100644 reporting-api/src/test/resources/test-persistence.xml diff --git a/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java b/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java index 268d54f56..fd2bfc794 100644 --- a/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java +++ b/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.test; +import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -34,4 +35,14 @@ public class EntityManagerService { public EntityManager getEntityManager() { return em; } + + public void doSqlUpdate(String query) { + em.createNativeQuery(query).executeUpdate(); + } + + @SuppressWarnings("unchecked") + public List doSqlQuery(String query) { + return em.createNativeQuery(query).getResultList(); + } + } diff --git a/kx-api/pom.xml b/kx-api/pom.xml index fa20c8e24..aec69a5c6 100644 --- a/kx-api/pom.xml +++ b/kx-api/pom.xml @@ -260,21 +260,7 @@ 7.0.9 test - org.awaitility awaitility diff --git a/kx-api/src/test/resources/arquillian.xml b/kx-api/src/test/resources/arquillian.xml index a22ccb774..707677827 100644 --- a/kx-api/src/test/resources/arquillian.xml +++ b/kx-api/src/test/resources/arquillian.xml @@ -1,7 +1,7 @@ + + 4.0.0 + + de.ipb-halle + reporting-api + 1.0.0 + jar + + REPORTING-API + http://github.com/ipb-halle/CRIMSy + + + UTF-8 + + + 5.3.26.Final + 9.2.0.4-591 + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + pentaho-releases + + + https://repo.orl.eng.hitachivantara.com/artifactory/pnt-mvn + + true + true + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + + + + + + + make-assembly + package + + single + + + + + + + + + + + + + de.ipb-halle + crimsy-api + 1.0.0 + + + + + de.ipb-halle + crimsy-test + 1.0.0 + test + + + + + org.pentaho.reporting.engine + classic-core + ${pentaho.version} + provided + + + + + junit + junit + 4.13.1 + test + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + test + + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-ehcache + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-core + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-validator + 5.3.6.Final + provided + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + org.apache.tomee + javaee-api + 7.0-2 + provided + + + + + org.postgresql + postgresql + 42.4.3 + + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + test + + + org.testcontainers + postgresql + 1.16.3 + test + + + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha6 + test + + + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.6 + pom + test + + + + + org.apache.tomee + arquillian-tomee-embedded + 7.0.9 + test + + + + org.awaitility + awaitility + 4.0.3 + test + + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + commons-io + commons-io + + + + + + diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java b/reporting-api/src/main/java/de/ipb_halle/reporting/report/Report.java similarity index 93% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/report/Report.java index 982508406..5dd36991c 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/Report.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/report/Report.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.reporting.report; import de.ipb_halle.crimsy_api.DTO; diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportEntity.java b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportEntity.java similarity index 96% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportEntity.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportEntity.java index b90674198..816f4bf7b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportEntity.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportEntity.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.reporting.report; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportService.java b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportService.java similarity index 97% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportService.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportService.java index 93236ccc0..1a7dd73a8 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportService.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportService.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.reporting.report; import java.util.ArrayList; import java.util.List; diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportType.java b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java similarity index 95% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportType.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java index 636259181..7989d6330 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportType.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.reporting.report; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil; diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportServiceTest.java b/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportServiceTest.java similarity index 65% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportServiceTest.java rename to reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportServiceTest.java index 4da1169db..9f9ef0493 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportServiceTest.java +++ b/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportServiceTest.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.reporting.report; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; @@ -29,11 +29,13 @@ import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import de.ipb_halle.lbac.base.TestBase; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** @@ -41,15 +43,25 @@ */ @ExtendWith(PostgresqlContainerExtension.class) @ExtendWith(ArquillianExtension.class) -public class ReportServiceTest extends TestBase { +public class ReportServiceTest { private static final long serialVersionUID = 1L; + private static final String INSERT_REPORT_FORMAT = "INSERT INTO reports (context, name, source) VALUES ('%s','%s','%s')"; + + @Inject + private EntityManagerService ems; @Inject private ReportService reportService; @Deployment public static WebArchive createDeployment() { - return prepareDeployment("ReportServiceTest.war").addClasses(ReportService.class); + WebArchive archive = ShrinkWrap.create(WebArchive.class, "ReportServiceTest.war") + .addClass(ReportService.class) + .addClass(EntityManagerService.class) + .addAsResource("PostgresqlContainerSchemaFiles") + .addAsWebInfResource("test-persistence.xml", "persistence.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; } @Test @@ -64,4 +76,9 @@ public void test_loadByContext() { assertThat(reports, hasSize(1)); assertEquals("report1", reports.get(0).getName()); } -} \ No newline at end of file + + private void insertReport(String context, String name, String source) { + ems.doSqlUpdate(String.format(INSERT_REPORT_FORMAT, context, name, source)); + } + +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportTest.java b/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportTest.java similarity index 94% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportTest.java rename to reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportTest.java index 9de7d4201..e8f9fe6ea 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportTest.java +++ b/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportTest.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.reporting.report; import static org.junit.Assert.assertEquals; import org.junit.jupiter.api.Test; @@ -52,4 +52,4 @@ public void test_createEntity() { assertEquals("new name", newEntity.getName()); assertEquals("ghi", newEntity.getSource()); } -} \ No newline at end of file +} diff --git a/reporting-api/src/test/resources/PostgresqlContainerSchemaFiles b/reporting-api/src/test/resources/PostgresqlContainerSchemaFiles new file mode 100644 index 000000000..8fc81a1ec --- /dev/null +++ b/reporting-api/src/test/resources/PostgresqlContainerSchemaFiles @@ -0,0 +1,5 @@ +# +# Configuration of PostgresqlContainerExtension: +# schema files for testing +# +00001.sql diff --git a/reporting-api/src/test/resources/arquillian.xml b/reporting-api/src/test/resources/arquillian.xml new file mode 100644 index 000000000..707677827 --- /dev/null +++ b/reporting-api/src/test/resources/arquillian.xml @@ -0,0 +1,49 @@ + + + + + + + 8800 + + + + target/arquillian + + apiDS = new://Resource?type=DataSource + apiDS.JdbcDriver = org.postgresql.Driver + apiDS.JdbcUrl = jdbc:postgresql://localhost:65432/lbac?charSet=UTF-8 + apiDS.UserName = lbac + apiDS.Password = lbac + apiDS.JtaManaged = true + apiDS.LogSql = true + # + # + tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar + tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ + postgresql*.jar,\ + log4j*.jar,slf4j*.jar,hibernate*.jar + + true + + + + + + + diff --git a/reporting-api/src/test/resources/schema/00001.sql b/reporting-api/src/test/resources/schema/00001.sql new file mode 100644 index 000000000..01e30a7d7 --- /dev/null +++ b/reporting-api/src/test/resources/schema/00001.sql @@ -0,0 +1,100 @@ +/* + * Leibniz Bioactives Cloud + * Init script for database postgres 12.6 + * + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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. + * + *========================================================= + * + * Notes: none + * + *========================================================= + * + * define global vars + */ +\set LBAC_SCHEMA_VERSION '\'00001\'' + +\set LBAC_SCHEMA lbac +\set LBAC_DATABASE lbac +\set LBAC_USER lbac +\set LBAC_PW lbac +-- quoted stuff -- +\set LBAC_SCHEMA_QUOTED '\'' :LBAC_SCHEMA '\'' +\set LBAC_DATABASE_QUOTED '\'' :LBAC_DATABASE '\'' +\set LBAC_USER_QUOTED '\'' :LBAC_USER '\'' +\set LBAC_PW_QUOTED '\'' :LBAC_PW '\'' + +/* + *========================================================= + * + * terminate all session from database lbac -- + * getting db exclusive -- + */ +SELECT pg_terminate_backend(pg_stat_activity.pid) +FROM pg_stat_activity +WHERE pg_stat_activity.datname = :LBAC_DATABASE_QUOTED + AND pid <> pg_backend_pid(); + +/* + * clean up + */ +-- the following statement fails if LBAC_USER is not known! +-- REASSIGN OWNED BY :LBAC_USER TO postgres; +DROP SCHEMA IF EXISTS :LBAC_SCHEMA CASCADE; +DROP DATABASE IF EXISTS :LBAC_DATABASE; +DROP USER IF EXISTS :LBAC_USER; + +/* + * (re-)create database objects + */ +-- roles -- +CREATE USER :LBAC_USER PASSWORD :LBAC_PW_QUOTED; +-- db -- +CREATE DATABASE :LBAC_DATABASE WITH ENCODING 'UTF8' OWNER :LBAC_USER; + +\connect :LBAC_DATABASE + +-- skipping Bingo database extension (chemistry) -- + +-- schema -- +CREATE SCHEMA AUTHORIZATION :LBAC_USER; + +-- adjust schema search path -- +ALTER USER :LBAC_USER SET search_path to :LBAC_SCHEMA,public; + +-- privileges -- +GRANT USAGE ON SCHEMA :LBAC_SCHEMA, public TO :LBAC_USER; +GRANT CONNECT, TEMPORARY, TEMP ON DATABASE :LBAC_DATABASE to :LBAC_USER; +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA :LBAC_SCHEMA to :LBAC_USER; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to :LBAC_USER; +REVOKE ALL ON ALL TABLES IN SCHEMA :LBAC_SCHEMA FROM public; + +-- check for usefull extensions and install it -- +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +\connect - :LBAC_USER + +BEGIN TRANSACTION; + +-- tables -- + +CREATE TABLE reports ( + id SERIAL NOT NULL PRIMARY KEY, + context VARCHAR NOT NULL, + name VARCHAR NOT NULL, + source VARCHAR NOT NULL); + +COMMIT; + diff --git a/reporting-api/src/test/resources/test-persistence.xml b/reporting-api/src/test/resources/test-persistence.xml new file mode 100644 index 000000000..bd80738b5 --- /dev/null +++ b/reporting-api/src/test/resources/test-persistence.xml @@ -0,0 +1,54 @@ + + + + + + + + LBAC api + apiDS + + org.hibernate.jpa.HibernatePersistenceProvider + + de.ipb_halle.reporting.report.ReportEntity + + true + + + + + + + + + + + + + + + + + diff --git a/ui/pom.xml b/ui/pom.xml index b952d8f31..3b7c2e1c8 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -280,6 +280,13 @@ 1.0 + + + de.ipb-halle + reporting-api + 1.0.0 + + de.ipb-halle @@ -288,7 +295,6 @@ test - com.googlecode.owasp-java-html-sanitizer diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java b/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java index 288687fe1..62b244c88 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java @@ -33,9 +33,7 @@ import de.ipb_halle.lbac.material.common.service.MaterialService; import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.lbac.reporting.report.Report; import de.ipb_halle.lbac.reporting.report.ReportMgr; -import de.ipb_halle.lbac.reporting.report.ReportType; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.items.ItemHistory; import de.ipb_halle.lbac.items.bean.aliquot.createsolution.CreateSolutionBean; @@ -44,6 +42,8 @@ import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.NonEmpty; +import de.ipb_halle.reporting.report.Report; +import de.ipb_halle.reporting.report.ReportType; import java.io.Serializable; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java index 8cf579e4e..1e811abf2 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java @@ -31,9 +31,7 @@ import de.ipb_halle.lbac.material.MaterialType; import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.lbac.reporting.report.Report; import de.ipb_halle.lbac.reporting.report.ReportMgr; -import de.ipb_halle.lbac.reporting.report.ReportType; import de.ipb_halle.lbac.util.resources.ResourceLocation; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.material.JsfMessagePresenter; @@ -42,6 +40,8 @@ import de.ipb_halle.lbac.material.composition.Concentration; import de.ipb_halle.lbac.material.structure.Molecule; import de.ipb_halle.lbac.util.NonEmpty; +import de.ipb_halle.reporting.report.Report; +import de.ipb_halle.reporting.report.ReportType; import java.io.Serializable; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java index 54a13f39e..9f515981d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java @@ -20,7 +20,7 @@ import java.io.Serializable; import java.util.Map; -import de.ipb_halle.lbac.reporting.report.ReportType; +import de.ipb_halle.reporting.report.ReportType; /** * Serializable data class to be used as input data of reporting jobs. @@ -51,4 +51,4 @@ public ReportType getType() { public Map getParameters() { return parameters; } -} \ No newline at end of file +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java index ec7f64c3d..dbf70aaba 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java @@ -20,6 +20,9 @@ import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.reporting.job.ReportJobPojo; import de.ipb_halle.lbac.reporting.job.ReportJobService; +import de.ipb_halle.reporting.report.Report; +import de.ipb_halle.reporting.report.ReportService; +import de.ipb_halle.reporting.report.ReportType; import java.util.Comparator; import java.util.List; diff --git a/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java b/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java index 3f24ed00e..f5b988c39 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java +++ b/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java @@ -33,9 +33,9 @@ import de.ipb_halle.lbac.material.common.service.MaterialService; import de.ipb_halle.lbac.reporting.job.ReportJobService; import de.ipb_halle.lbac.reporting.report.ReportMgr; -import de.ipb_halle.lbac.reporting.report.ReportService; import de.ipb_halle.lbac.util.pref.PreferenceService; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; +import de.ipb_halle.reporting.report.ReportService; import org.jboss.shrinkwrap.api.spec.WebArchive; diff --git a/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java b/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java index 6927da0d2..673025640 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java +++ b/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java @@ -31,8 +31,8 @@ import de.ipb_halle.lbac.project.ProjectService; import de.ipb_halle.lbac.reporting.job.ReportJobService; import de.ipb_halle.lbac.reporting.report.ReportMgr; -import de.ipb_halle.lbac.reporting.report.ReportService; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; +import de.ipb_halle.reporting.report.ReportService; import org.jboss.shrinkwrap.api.spec.WebArchive; diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java index 4b7ccee98..03bd85f37 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java @@ -26,8 +26,9 @@ import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; import static de.ipb_halle.lbac.device.job.JobType.REPORT; import static de.ipb_halle.lbac.reporting.job.ReportJobService.MAX_AGE; -import static de.ipb_halle.lbac.reporting.report.ReportType.CSV; -import static de.ipb_halle.lbac.reporting.report.ReportType.PDF; +import static de.ipb_halle.reporting.report.ReportType.CSV; +import static de.ipb_halle.reporting.report.ReportType.PDF; +// xxxxx re-enable XLSX!!! //import static de.ipb_halle.lbac.reporting.report.ReportType.XLSX; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java index 912524b6c..eb3d8fa47 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java @@ -21,7 +21,7 @@ import static de.ipb_halle.lbac.device.job.JobStatus.FAILED; import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; import static de.ipb_halle.lbac.device.job.JobType.REPORT; -import static de.ipb_halle.lbac.reporting.report.ReportType.CSV; +import static de.ipb_halle.reporting.report.ReportType.CSV; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -170,4 +170,4 @@ private boolean isInTempReportsDir(String filename) { private boolean tempReportsDirIsEmpty() { return tempReportsDir.list().length == 0; } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java index 0ffbe6655..d77b897a4 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java @@ -19,7 +19,7 @@ import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; import static de.ipb_halle.lbac.reporting.report.ReportMgr.DEFAULT_NAME; -import static de.ipb_halle.lbac.reporting.report.ReportType.PDF; +import static de.ipb_halle.reporting.report.ReportType.PDF; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -48,6 +48,10 @@ import de.ipb_halle.test.ManagedExecutorServiceMock; import de.ipb_halle.lbac.reporting.job.ReportJobPojo; import de.ipb_halle.lbac.reporting.job.ReportJobService; +import de.ipb_halle.reporting.report.Report; +import de.ipb_halle.reporting.report.ReportEntity; +import de.ipb_halle.reporting.report.ReportService; +import de.ipb_halle.reporting.report.ReportType; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** diff --git a/ui/src/test/resources/test-persistence.xml b/ui/src/test/resources/test-persistence.xml index 041155cac..aac18637c 100644 --- a/ui/src/test/resources/test-persistence.xml +++ b/ui/src/test/resources/test-persistence.xml @@ -91,9 +91,9 @@ de.ipb_halle.lbac.forum.TopicEntity de.ipb_halle.lbac.forum.PostingEntity de.ipb_halle.lbac.util.pref.PreferenceEntity - de.ipb_halle.lbac.reporting.report.ReportEntity de.ipb_halle.kx.file.FileObjectEntity de.ipb_halle.kx.termvector.TermVectorEntity + de.ipb_halle.reporting.report.ReportEntity de.ipb_halle.lbac.search.lang.HorrorEntity From e4a03f7df14d88f21cbefc75ec09b7ab62b0971c Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Wed, 25 Oct 2023 13:56:44 +0200 Subject: [PATCH 21/28] disentangle GlobalAdmissionContext and Reporting --- .../reporting/report/ReportsDirectory.java | 40 ++++++++++ .../admission/GlobalAdmissionContext.java | 8 -- .../lbac/reporting/job/ReportJobService.java | 7 +- .../mock/GlobalAdmissionContextMock.java | 38 +-------- .../java/de/ipb_halle/lbac/base/TestBase.java | 2 + .../reporting/job/ReportJobServiceTest.java | 6 +- .../reporting/mocks/ReportsDirectoryMock.java | 77 +++++++++++++++++++ 7 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java create mode 100644 ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java new file mode 100644 index 000000000..cab295c9c --- /dev/null +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java @@ -0,0 +1,40 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.reporting.report; + +import java.io.Serializable; +import javax.ejb.Singleton; +import javax.ejb.Startup; + + +/** + * + * @author fbroda + */ +@Singleton(name="reportsDirectory") +@Startup +public class ReportsDirectory implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final String REPORTS_DIRECTORY = "/data/tmp/reports/"; + + public String getReportsDirectory() { + return REPORTS_DIRECTORY; + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/GlobalAdmissionContext.java b/ui/src/main/java/de/ipb_halle/lbac/admission/GlobalAdmissionContext.java index 906e0ee3e..579e58a0b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/GlobalAdmissionContext.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/GlobalAdmissionContext.java @@ -52,7 +52,6 @@ public class GlobalAdmissionContext implements Serializable { private static final long serialVersionUID = 1L; public final static String PUBLIC_NODE_ID = "1e0f832b-3d9e-4ebb-9e68-5a9fc2d9bee8"; private static final String LBAC_PROPERTIES_PATH = "/data/conf/lbac_properties.xml"; - private static final String REPORTS_DIRECTORY = "/data/tmp/reports/"; public final static Integer PUBLIC_GROUP_ID = 1; public final static Integer PUBLIC_ACCOUNT_ID = 2; @@ -443,11 +442,4 @@ private void intruderLockoutUnlockRecord(String key) { public String getLbacPropertiesPath() { return LBAC_PROPERTIES_PATH; } - - /** - * @return path of reports directory - */ - public String getReportsDirectory() { - return REPORTS_DIRECTORY; - } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java index 9dc4f3524..93a8b385d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java @@ -52,6 +52,7 @@ import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.device.job.Job; import de.ipb_halle.lbac.device.job.JobService; +import de.ipb_halle.reporting.report.ReportsDirectory; /** * @@ -71,7 +72,7 @@ public class ReportJobService { private ManagedExecutorService managedExecutorService; @Inject - private GlobalAdmissionContext globalAdmissionContext; + private ReportsDirectory reportsDirectory; @Inject private JobService jobService; @@ -235,7 +236,7 @@ private ReportTask prepareTask(Job job) { } private String getReportsDirectory() { - return globalAdmissionContext.getReportsDirectory(); + return reportsDirectory.getReportsDirectory(); } private byte[] serialize(Object o) { @@ -327,4 +328,4 @@ private void deleteOrphanedReportFiles() throws IOException { public void setManagedExecutorService(ManagedExecutorService managedExecutorService) { this.managedExecutorService = managedExecutorService; } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/mock/GlobalAdmissionContextMock.java b/ui/src/test/java/de/ipb_halle/lbac/admission/mock/GlobalAdmissionContextMock.java index 873de3a90..2c3a194cb 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/mock/GlobalAdmissionContextMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/mock/GlobalAdmissionContextMock.java @@ -22,13 +22,9 @@ import java.nio.file.Files; import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.ejb.Lock; -import javax.ejb.LockType; import javax.ejb.Singleton; import javax.ejb.Startup; -import org.apache.commons.io.FileUtils; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; @@ -42,46 +38,14 @@ public class GlobalAdmissionContextMock extends GlobalAdmissionContext { private static final long serialVersionUID = 1L; private static final String TEST_LBAC_PROPERTIES_PATH = "target/test-classes/keystore/lbac_properties.xml"; - /* - * The reports directory is initialized lazily, i.e. only when requested. Most - * tests won't need it. The shutdown() method needs to take care for its - * recursive deletion. Marking it with File.deleteOnExit() may not work, because - * it does not delete recursively. - */ - private File reportsDirectory = null; - @PostConstruct private void initialize() { super.init(); } - @PreDestroy - private void shutdown() throws IOException { - if (reportsDirectory != null) { - FileUtils.deleteDirectory(reportsDirectory); - reportsDirectory = null; - } - } - - private void initializeReportsDirectory() { - try { - reportsDirectory = Files.createTempDirectory("reports").toFile(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } @Override public String getLbacPropertiesPath() { return TEST_LBAC_PROPERTIES_PATH; } - - @Lock(LockType.WRITE) - @Override - public String getReportsDirectory() { - if (reportsDirectory == null) { - initializeReportsDirectory(); - } - return reportsDirectory.getAbsolutePath(); - } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java index d279bf747..66552c5ce 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java @@ -46,6 +46,7 @@ import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.admission.MembershipService; import de.ipb_halle.lbac.project.ProjectService; +import de.ipb_halle.lbac.reporting.mocks.ReportsDirectoryMock; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.scope.SessionScopeContext; import de.ipb_halle.scope.SessionScopeResetEvent; @@ -148,6 +149,7 @@ public class TestBase implements Serializable { public static WebArchive prepareDeployment(String archiveName) { WebArchive archive = ShrinkWrap.create(WebArchive.class, archiveName) .addClass(GlobalAdmissionContextMock.class) + .addClass(ReportsDirectoryMock.class) .addClass(GlobalVersions.class) .addClass(ACListService.class) .addClass(CloudService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java index 03bd85f37..7c7eabb1e 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.reporting.job; +import de.ipb_halle.reporting.report.ReportsDirectory; import de.ipb_halle.test.ManagedExecutorServiceMock; import static de.ipb_halle.lbac.device.job.JobService.CONDITION_JOBTYPE; import static de.ipb_halle.lbac.device.job.JobService.CONDITION_STATUS; @@ -60,7 +61,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; -import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.device.job.Job; import de.ipb_halle.lbac.device.job.JobService; @@ -81,7 +81,7 @@ public class ReportJobServiceTest extends TestBase { private JobService jobService; @Inject - private GlobalAdmissionContext globalAdmissionContext; + private ReportsDirectory reportsDirectory; @Deployment public static WebArchive createDeployment() { @@ -352,7 +352,7 @@ public void test_cleanUpOldJobsAndReportFiles() throws IOException { } private File createTempFileInReportsDirectory() throws IOException { - File reportsDir = new File(globalAdmissionContext.getReportsDirectory()); + File reportsDir = new File(reportsDirectory.getReportsDirectory()); File tempFile = File.createTempFile("ReportJobServiceTest", "test", reportsDir); tempFile.deleteOnExit(); return tempFile; diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java new file mode 100644 index 000000000..c619b4d5b --- /dev/null +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java @@ -0,0 +1,77 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.reporting.mocks; + +import de.ipb_halle.reporting.report.ReportsDirectory; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.annotation.PreDestroy; +import javax.ejb.Lock; +import javax.ejb.LockType; +import javax.ejb.Singleton; +import javax.ejb.Startup; + +import org.apache.commons.io.FileUtils; + +/** + * + * @author fbroda, flange + */ +@Singleton(name="reportsDirectory") +@Startup +public class ReportsDirectoryMock extends ReportsDirectory { + + private static final long serialVersionUID = 1L; + + /* + * The reports directory is initialized lazily, i.e. only when requested. Most + * tests won't need it. The shutdown() method needs to take care for its + * recursive deletion. Marking it with File.deleteOnExit() may not work, because + * it does not delete recursively. + */ + private File reportsDirectory = null; + + @PreDestroy + private void shutdown() throws IOException { + if (reportsDirectory != null) { + FileUtils.deleteDirectory(reportsDirectory); + reportsDirectory = null; + } + } + + private void initializeReportsDirectory() { + try { + reportsDirectory = Files.createTempDirectory("reports").toFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Lock(LockType.WRITE) + @Override + public String getReportsDirectory() { + if (reportsDirectory == null) { + initializeReportsDirectory(); + } + return reportsDirectory.getAbsolutePath(); + } +} From 23fe1b90327518b14601f1019893aeb6e0c3d486 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Fri, 10 Nov 2023 15:45:22 +0100 Subject: [PATCH 22/28] Disentangled Reporting from UI - major changes to code base (all unit tests green) - integration test missing - external print service (agency) currently not usable; need to adjust URL at least --- agency/pom.xml | 8 +- .../{lbac/device => }/job/Agency.java | 4 +- .../{lbac/device => }/job/Handler.java | 4 +- {agency-api => crimsy-job-api}/LICENSE | 0 {agency-api => crimsy-job-api}/README.md | 2 +- {agency-api => crimsy-job-api}/pom.xml | 19 +- .../java/de/ipb_halle}/job/JobStatus.java | 4 +- .../main/java/de/ipb_halle}/job/JobType.java | 4 +- .../java/de/ipb_halle}/job/JobWebClient.java | 4 +- .../main/java/de/ipb_halle}/job/NetJob.java | 4 +- .../java/de/ipb_halle}/job/RequestType.java | 4 +- .../de/ipb_halle}/job/TokenGenerator.java | 4 +- .../de/ipb_halle}/job/TokenGeneratorTest.java | 2 +- crimsy-job-storage/LICENSE | 201 +++++++++ crimsy-job-storage/README.md | 5 + crimsy-job-storage/pom.xml | 252 +++++++++++ .../src/main/java/de/ipb_halle}/job/Job.java | 73 ++-- .../java/de/ipb_halle}/job/JobEntity.java | 16 +- .../java/de/ipb_halle}/job/JobService.java | 87 ++-- .../java/de/ipb_halle/job/JobServiceImpl.java | 32 ++ .../de/ipb_halle}/job/JobServiceTest.java | 83 ++-- .../test/java/de/ipb_halle/job/TestJob.java | 29 ++ .../resources/PostgresqlContainerSchemaFiles | 5 + .../src/test/resources/arquillian.xml | 49 +++ .../src/test/resources/schema/00001.sql | 113 +++++ .../src/test/resources/test-persistence.xml | 54 +++ .../ipb_halle/test/EntityManagerService.java | 29 +- pom.xml | 3 +- .../reporting/{report => }/Report.java | 2 +- .../ipb_halle/reporting/ReportDataPojo.java | 89 ++++ .../reporting/{report => }/ReportEntity.java | 2 +- .../reporting/{report => }/ReportService.java | 2 +- .../de/ipb_halle/reporting/ReportType.java | 39 ++ .../{report => }/ReportsDirectory.java | 2 +- .../{report => }/ReportServiceTest.java | 2 +- .../reporting/{report => }/ReportTest.java | 2 +- .../src/test/resources/test-persistence.xml | 2 +- reporting/pom.xml | 403 ++++++++++++++++++ .../de/ipb_halle/reporting/ReportBuilder.java | 62 +-- .../reporting}/ReportSchedulingService.java | 14 +- .../de/ipb_halle/reporting}/ReportTask.java | 30 +- .../de/ipb_halle/reporting/ReportingJob.java | 77 ++++ .../reporting/ReportingJobService.java | 161 +++---- .../ipb_halle/reporting}/ReportTaskTest.java | 66 +-- .../reporting/ReportingJobServiceTest.java | 296 +++++++++++++ .../reporting/mocks/ReportsDirectoryMock.java | 77 ++++ .../resources/PostgresqlContainerSchemaFiles | 5 + reporting/src/test/resources/arquillian.xml | 49 +++ .../src/test/resources/reports/readme.md | 0 .../test/resources/reports/testReport1.prpt | Bin .../reports/testReport1/META-INF/manifest.xml | 0 .../resources/reports/testReport1/content.xml | 0 .../reports/testReport1/datadefinition.xml | 0 .../reports/testReport1/dataschema.xml | 0 .../testReport1/datasources/compound-ds.xml | 0 .../testReport1/datasources/sql-ds.xml | 0 .../resources/reports/testReport1/layout.xml | 0 .../resources/reports/testReport1/meta.xml | 0 .../resources/reports/testReport1/mimetype | 0 .../reports/testReport1/settings.xml | 0 .../resources/reports/testReport1/styles.xml | 0 .../testReport1/translations.properties | 0 reporting/src/test/resources/schema/00001.sql | 122 ++++++ .../src/test/resources/test-persistence.xml | 55 +++ ui/pom.xml | 91 +--- .../lbac/admission/SystemSettings.java | 1 - .../ipb_halle/lbac/device/job/PrintJob.java | 76 ++++ .../lbac/device/job/PrintJobService.java | 58 +++ ...obWebService.java => PrintWebService.java} | 43 +- .../device/print/AbstractPrintDriver.java | 11 +- .../lbac/device/print/PrintAdminBean.java | 16 +- .../lbac/device/print/PrintBean.java | 12 +- .../lbac/device/print/PrintDriver.java | 4 +- .../lbac/device/print/ZebraE2Driver.java | 2 - .../lbac/globals/InitApplication.java | 6 + .../lbac/globals/health/HealthState.java | 1 + .../lbac/globals/health/HealthStateCheck.java | 13 + .../globals/health/HealthStateRepair.java | 25 +- .../lbac/items/bean/ItemOverviewBean.java | 6 +- .../common/bean/MaterialOverviewBean.java | 6 +- .../ipb_halle/lbac/reporting/ReportJob.java | 123 ++++++ .../lbac/reporting/ReportJobService.java | 154 +++++++ .../reporting/{report => }/ReportMgr.java | 13 +- .../{jobview => }/ReportingJobsBean.java | 46 +- .../lbac/reporting/job/ReportJobPojo.java | 54 --- .../reporting/jobview/ReportingJobWapper.java | 79 ---- .../lbac/webservice/ApplicationConfig.java | 4 +- .../lbac/i18n/messages_de.properties | 4 +- .../lbac/i18n/messages_en.properties | 4 +- .../ipb_halle/lbac/EntityManagerService.java | 61 --- .../admission/UserPluginSettingsBeanTest.java | 4 +- .../UserTimeZoneSettingsBeanTest.java | 4 +- .../ipb_halle/lbac/base/ContainerCreator.java | 2 +- .../de/ipb_halle/lbac/base/ItemCreator.java | 2 +- .../ipb_halle/lbac/base/MaterialCreator.java | 2 +- .../java/de/ipb_halle/lbac/base/TestBase.java | 4 +- .../service/ContainerServiceTest.java | 10 +- .../lbac/database/SQLFunctionsTest.java | 4 +- .../device/print/PrintBeanDeployment.java | 8 +- .../lbac/device/print/PrinterTest.java | 28 +- .../lbac/forum/ForumServiceTest.java | 2 +- .../ipb_halle/lbac/items/ItemDeployment.java | 10 +- .../lbac/items/service/ItemServiceTest.java | 10 +- .../lbac/material/MaterialDeployment.java | 10 +- .../lbac/projects/ProjectServiceTest.java | 22 +- .../lbac/reporting/ReportJobServiceTest.java | 244 +++++++++++ ...gJobWapperTest.java => ReportJobTest.java} | 53 +-- .../reporting/{report => }/ReportMgrTest.java | 61 +-- .../{jobview => }/ReportingJobsBeanTest.java | 38 +- .../reporting/job/ReportJobServiceTest.java | 391 ----------------- .../reporting/mocks/ReportsDirectoryMock.java | 4 +- .../lbac/util/pref/PreferenceServiceTest.java | 2 +- ui/src/test/resources/test-persistence.xml | 81 ++-- ui/web/WEB-INF/persistence.xml | 86 ++-- ui/web/WEB-INF/templates/myReports.xhtml | 18 +- 115 files changed, 3338 insertions(+), 1328 deletions(-) rename agency/src/main/java/de/ipb_halle/{lbac/device => }/job/Agency.java (98%) rename agency/src/main/java/de/ipb_halle/{lbac/device => }/job/Handler.java (98%) rename {agency-api => crimsy-job-api}/LICENSE (100%) rename {agency-api => crimsy-job-api}/README.md (82%) rename {agency-api => crimsy-job-api}/pom.xml (85%) rename {agency-api/src/main/java/de/ipb_halle/lbac/device => crimsy-job-api/src/main/java/de/ipb_halle}/job/JobStatus.java (93%) rename {agency-api/src/main/java/de/ipb_halle/lbac/device => crimsy-job-api/src/main/java/de/ipb_halle}/job/JobType.java (93%) rename {agency/src/main/java/de/ipb_halle/lbac/device => crimsy-job-api/src/main/java/de/ipb_halle}/job/JobWebClient.java (96%) rename {agency-api/src/main/java/de/ipb_halle/lbac/device => crimsy-job-api/src/main/java/de/ipb_halle}/job/NetJob.java (97%) rename {agency-api/src/main/java/de/ipb_halle/lbac/device => crimsy-job-api/src/main/java/de/ipb_halle}/job/RequestType.java (95%) rename {agency-api/src/main/java/de/ipb_halle/lbac/device => crimsy-job-api/src/main/java/de/ipb_halle}/job/TokenGenerator.java (97%) rename {agency-api/src/test/java/de/ipb_halle/lbac/device => crimsy-job-api/src/test/java/de/ipb_halle}/job/TokenGeneratorTest.java (97%) create mode 100644 crimsy-job-storage/LICENSE create mode 100644 crimsy-job-storage/README.md create mode 100644 crimsy-job-storage/pom.xml rename {ui/src/main/java/de/ipb_halle/lbac/device => crimsy-job-storage/src/main/java/de/ipb_halle}/job/Job.java (73%) rename {ui/src/main/java/de/ipb_halle/lbac/device => crimsy-job-storage/src/main/java/de/ipb_halle}/job/JobEntity.java (89%) rename {ui/src/main/java/de/ipb_halle/lbac/device => crimsy-job-storage/src/main/java/de/ipb_halle}/job/JobService.java (70%) create mode 100644 crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceImpl.java rename {ui/src/test/java/de/ipb_halle/lbac/device => crimsy-job-storage/src/test/java/de/ipb_halle}/job/JobServiceTest.java (62%) create mode 100644 crimsy-job-storage/src/test/java/de/ipb_halle/job/TestJob.java create mode 100644 crimsy-job-storage/src/test/resources/PostgresqlContainerSchemaFiles create mode 100644 crimsy-job-storage/src/test/resources/arquillian.xml create mode 100644 crimsy-job-storage/src/test/resources/schema/00001.sql create mode 100644 crimsy-job-storage/src/test/resources/test-persistence.xml rename reporting-api/src/main/java/de/ipb_halle/reporting/{report => }/Report.java (97%) create mode 100644 reporting-api/src/main/java/de/ipb_halle/reporting/ReportDataPojo.java rename reporting-api/src/main/java/de/ipb_halle/reporting/{report => }/ReportEntity.java (98%) rename reporting-api/src/main/java/de/ipb_halle/reporting/{report => }/ReportService.java (98%) create mode 100644 reporting-api/src/main/java/de/ipb_halle/reporting/ReportType.java rename reporting-api/src/main/java/de/ipb_halle/reporting/{report => }/ReportsDirectory.java (96%) rename reporting-api/src/test/java/de/ipb_halle/reporting/{report => }/ReportServiceTest.java (98%) rename reporting-api/src/test/java/de/ipb_halle/reporting/{report => }/ReportTest.java (97%) create mode 100644 reporting/pom.xml rename reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java => reporting/src/main/java/de/ipb_halle/reporting/ReportBuilder.java (50%) rename {ui/src/main/java/de/ipb_halle/lbac/reporting/job => reporting/src/main/java/de/ipb_halle/reporting}/ReportSchedulingService.java (85%) rename {ui/src/main/java/de/ipb_halle/lbac/reporting/job => reporting/src/main/java/de/ipb_halle/reporting}/ReportTask.java (81%) create mode 100644 reporting/src/main/java/de/ipb_halle/reporting/ReportingJob.java rename ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java => reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java (58%) rename {ui/src/test/java/de/ipb_halle/lbac/reporting/job => reporting/src/test/java/de/ipb_halle/reporting}/ReportTaskTest.java (67%) create mode 100644 reporting/src/test/java/de/ipb_halle/reporting/ReportingJobServiceTest.java create mode 100644 reporting/src/test/java/de/ipb_halle/reporting/mocks/ReportsDirectoryMock.java create mode 100644 reporting/src/test/resources/PostgresqlContainerSchemaFiles create mode 100644 reporting/src/test/resources/arquillian.xml rename {ui => reporting}/src/test/resources/reports/readme.md (100%) rename {ui => reporting}/src/test/resources/reports/testReport1.prpt (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/META-INF/manifest.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/content.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/datadefinition.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/dataschema.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/datasources/compound-ds.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/datasources/sql-ds.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/layout.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/meta.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/mimetype (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/settings.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/styles.xml (100%) rename {ui => reporting}/src/test/resources/reports/testReport1/translations.properties (100%) create mode 100644 reporting/src/test/resources/schema/00001.sql create mode 100644 reporting/src/test/resources/test-persistence.xml create mode 100644 ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJob.java create mode 100644 ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJobService.java rename ui/src/main/java/de/ipb_halle/lbac/device/job/{JobWebService.java => PrintWebService.java} (81%) create mode 100644 ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJob.java create mode 100644 ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJobService.java rename ui/src/main/java/de/ipb_halle/lbac/reporting/{report => }/ReportMgr.java (91%) rename ui/src/main/java/de/ipb_halle/lbac/reporting/{jobview => }/ReportingJobsBean.java (60%) delete mode 100644 ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java delete mode 100644 ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapper.java delete mode 100644 ui/src/test/java/de/ipb_halle/lbac/EntityManagerService.java create mode 100644 ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobServiceTest.java rename ui/src/test/java/de/ipb_halle/lbac/reporting/{jobview/ReportingJobWapperTest.java => ReportJobTest.java} (62%) rename ui/src/test/java/de/ipb_halle/lbac/reporting/{report => }/ReportMgrTest.java (70%) rename ui/src/test/java/de/ipb_halle/lbac/reporting/{jobview => }/ReportingJobsBeanTest.java (78%) delete mode 100644 ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java diff --git a/agency/pom.xml b/agency/pom.xml index 0e257de2c..fa7733e4c 100644 --- a/agency/pom.xml +++ b/agency/pom.xml @@ -76,7 +76,7 @@ - de.ipb_halle.lbac.device.job.Agency + de.ipb_halle.job.Agency @@ -133,7 +133,7 @@ - de.ipb_halle.lbac.device.job.Agency + de.ipb_halle.job.Agency @@ -179,10 +179,10 @@ 1.3.1 - + de.ipb-halle - crimsy-agency-api + crimsy-job-api 1.0 diff --git a/agency/src/main/java/de/ipb_halle/lbac/device/job/Agency.java b/agency/src/main/java/de/ipb_halle/job/Agency.java similarity index 98% rename from agency/src/main/java/de/ipb_halle/lbac/device/job/Agency.java rename to agency/src/main/java/de/ipb_halle/job/Agency.java index 32b0fc4ec..82f39c67d 100644 --- a/agency/src/main/java/de/ipb_halle/lbac/device/job/Agency.java +++ b/agency/src/main/java/de/ipb_halle/job/Agency.java @@ -1,6 +1,6 @@ /* * CRIMSy Agency - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import java.io.BufferedReader; import java.io.FileReader; diff --git a/agency/src/main/java/de/ipb_halle/lbac/device/job/Handler.java b/agency/src/main/java/de/ipb_halle/job/Handler.java similarity index 98% rename from agency/src/main/java/de/ipb_halle/lbac/device/job/Handler.java rename to agency/src/main/java/de/ipb_halle/job/Handler.java index e2761f790..8bdc396e4 100644 --- a/agency/src/main/java/de/ipb_halle/lbac/device/job/Handler.java +++ b/agency/src/main/java/de/ipb_halle/job/Handler.java @@ -1,6 +1,6 @@ /* * CRIMSy Agency - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import java.io.IOException; import java.util.List; diff --git a/agency-api/LICENSE b/crimsy-job-api/LICENSE similarity index 100% rename from agency-api/LICENSE rename to crimsy-job-api/LICENSE diff --git a/agency-api/README.md b/crimsy-job-api/README.md similarity index 82% rename from agency-api/README.md rename to crimsy-job-api/README.md index b702a0959..88656472a 100644 --- a/agency-api/README.md +++ b/crimsy-job-api/README.md @@ -1,4 +1,4 @@ -# crimsy-agency-api +# crimsy-job-api API for communication between CRIMSy and crimsy-agency. diff --git a/agency-api/pom.xml b/crimsy-job-api/pom.xml similarity index 85% rename from agency-api/pom.xml rename to crimsy-job-api/pom.xml index 5f016f1bf..181bc332b 100644 --- a/agency-api/pom.xml +++ b/crimsy-job-api/pom.xml @@ -23,11 +23,11 @@ 4.0.0 de.ipb-halle - crimsy-agency-api + crimsy-job-api 1.0 jar - CRIMSy Agency API + CRIMSy Job API http://github.com/ipb-halle/CRIMSy @@ -95,6 +95,21 @@ + + + de.ipb-halle + crimsy-api + 1.0.0 + + + + + org.apache.tomee + tomee-jaxrs + 7.0.5 + provided + + junit diff --git a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/JobStatus.java b/crimsy-job-api/src/main/java/de/ipb_halle/job/JobStatus.java similarity index 93% rename from agency-api/src/main/java/de/ipb_halle/lbac/device/job/JobStatus.java rename to crimsy-job-api/src/main/java/de/ipb_halle/job/JobStatus.java index 4ef9e0157..2b997a0b5 100644 --- a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/JobStatus.java +++ b/crimsy-job-api/src/main/java/de/ipb_halle/job/JobStatus.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import javax.xml.bind.annotation.XmlEnum; diff --git a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/JobType.java b/crimsy-job-api/src/main/java/de/ipb_halle/job/JobType.java similarity index 93% rename from agency-api/src/main/java/de/ipb_halle/lbac/device/job/JobType.java rename to crimsy-job-api/src/main/java/de/ipb_halle/job/JobType.java index a7ac4d571..3caf74673 100644 --- a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/JobType.java +++ b/crimsy-job-api/src/main/java/de/ipb_halle/job/JobType.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import javax.xml.bind.annotation.XmlEnum; diff --git a/agency/src/main/java/de/ipb_halle/lbac/device/job/JobWebClient.java b/crimsy-job-api/src/main/java/de/ipb_halle/job/JobWebClient.java similarity index 96% rename from agency/src/main/java/de/ipb_halle/lbac/device/job/JobWebClient.java rename to crimsy-job-api/src/main/java/de/ipb_halle/job/JobWebClient.java index 2d606cfad..65108fb3a 100644 --- a/agency/src/main/java/de/ipb_halle/lbac/device/job/JobWebClient.java +++ b/crimsy-job-api/src/main/java/de/ipb_halle/job/JobWebClient.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import javax.ws.rs.core.MediaType; diff --git a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/NetJob.java b/crimsy-job-api/src/main/java/de/ipb_halle/job/NetJob.java similarity index 97% rename from agency-api/src/main/java/de/ipb_halle/lbac/device/job/NetJob.java rename to crimsy-job-api/src/main/java/de/ipb_halle/job/NetJob.java index 8b3853475..621778c00 100644 --- a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/NetJob.java +++ b/crimsy-job-api/src/main/java/de/ipb_halle/job/NetJob.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import java.io.Serializable; diff --git a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/RequestType.java b/crimsy-job-api/src/main/java/de/ipb_halle/job/RequestType.java similarity index 95% rename from agency-api/src/main/java/de/ipb_halle/lbac/device/job/RequestType.java rename to crimsy-job-api/src/main/java/de/ipb_halle/job/RequestType.java index fd3936635..d75c14077 100644 --- a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/RequestType.java +++ b/crimsy-job-api/src/main/java/de/ipb_halle/job/RequestType.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import javax.xml.bind.annotation.XmlEnum; diff --git a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/TokenGenerator.java b/crimsy-job-api/src/main/java/de/ipb_halle/job/TokenGenerator.java similarity index 97% rename from agency-api/src/main/java/de/ipb_halle/lbac/device/job/TokenGenerator.java rename to crimsy-job-api/src/main/java/de/ipb_halle/job/TokenGenerator.java index be3204460..03483ca5f 100644 --- a/agency-api/src/main/java/de/ipb_halle/lbac/device/job/TokenGenerator.java +++ b/crimsy-job-api/src/main/java/de/ipb_halle/job/TokenGenerator.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import java.util.Date; import java.security.Key; diff --git a/agency-api/src/test/java/de/ipb_halle/lbac/device/job/TokenGeneratorTest.java b/crimsy-job-api/src/test/java/de/ipb_halle/job/TokenGeneratorTest.java similarity index 97% rename from agency-api/src/test/java/de/ipb_halle/lbac/device/job/TokenGeneratorTest.java rename to crimsy-job-api/src/test/java/de/ipb_halle/job/TokenGeneratorTest.java index a7ef2b291..5ec1f26bb 100644 --- a/agency-api/src/test/java/de/ipb_halle/lbac/device/job/TokenGeneratorTest.java +++ b/crimsy-job-api/src/test/java/de/ipb_halle/job/TokenGeneratorTest.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import org.junit.Before; diff --git a/crimsy-job-storage/LICENSE b/crimsy-job-storage/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/crimsy-job-storage/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/crimsy-job-storage/README.md b/crimsy-job-storage/README.md new file mode 100644 index 000000000..12c2fac2d --- /dev/null +++ b/crimsy-job-storage/README.md @@ -0,0 +1,5 @@ +# crimsy-job-storage + +Database storage for jobs + +**Work in progress - may have bugs!** diff --git a/crimsy-job-storage/pom.xml b/crimsy-job-storage/pom.xml new file mode 100644 index 000000000..8aaf72439 --- /dev/null +++ b/crimsy-job-storage/pom.xml @@ -0,0 +1,252 @@ + + + 4.0.0 + + de.ipb-halle + crimsy-job-storage + 1.0 + jar + + CRIMSy Job DB Storage + http://github.com/ipb-halle/CRIMSy + + + UTF-8 + 5.3.26.Final + + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + maven-assembly-plugin + 2.6 + + + jar-with-dependencies + + + + + + + + + + make-assembly + package + + single + + + + + + + + + + + + + de.ipb-halle + crimsy-job-api + 1.0 + + + + + org.hibernate + hibernate-core + ${hibernate.version} + provided + + + + + org.apache.tomee + javaee-api + 7.0-2 + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + + + de.ipb-halle + crimsy-test + 1.0.0 + test + + + + + junit + junit + 4.13.1 + test + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + test + + + + org.testcontainers + postgresql + 1.16.3 + test + + + + org.postgresql + postgresql + 42.4.3 + test + + + + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha6 + test + + + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.6 + pom + test + + + + + org.apache.tomee + arquillian-tomee-embedded + 7.0.9 + test + + + + org.awaitility + awaitility + 4.0.3 + test + + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + commons-io + commons-io + + + + + + diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java b/crimsy-job-storage/src/main/java/de/ipb_halle/job/Job.java similarity index 73% rename from ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java rename to crimsy-job-storage/src/main/java/de/ipb_halle/job/Job.java index 38e166e00..af60b22e1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/job/Job.java +++ b/crimsy-job-storage/src/main/java/de/ipb_halle/job/Job.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,9 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import de.ipb_halle.crimsy_api.DTO; -import de.ipb_halle.lbac.admission.User; - import java.util.Date; /** @@ -27,30 +25,31 @@ * * @author fbroda */ -public class Job implements DTO { +public abstract class Job implements DTO { + private Integer jobid; private byte[] input; private Date jobdate; private JobType jobtype; private byte[] output; - private User owner; private String queue; private JobStatus status; + private Integer ownerId; - public Job() { + protected Job() { this.jobdate = new Date(); this.status = JobStatus.PENDING; } - public Job(JobEntity entity, User owner) { + protected Job(JobEntity entity) { this.jobid = entity.getJobId(); this.input = entity.getInput(); this.jobdate = entity.getJobDate(); - this.jobtype = entity.getJobType(); this.output = entity.getOutput(); - this.owner = owner; this.queue = entity.getQueue(); this.status = entity.getStatus(); + this.jobtype = entity.getJobType(); + this.ownerId = entity.getOwnerId(); } public JobEntity createEntity() { @@ -59,18 +58,15 @@ public JobEntity createEntity() { .setJobDate(this.jobdate) .setJobId(this.jobid) .setJobType(this.jobtype) + .setOwnerId(this.ownerId) .setOutput(this.output) .setQueue(this.queue) .setStatus(this.status); - - if (this.owner != null) { - e.setOwnerId(this.owner.getId()); - } return e; } public NetJob createNetJob() { - NetJob j = new NetJob() + NetJob nj = new NetJob() .setInput(this.input) .setJobDate(this.jobdate) .setJobId(this.jobid) @@ -78,11 +74,10 @@ public NetJob createNetJob() { .setOutput(this.output) .setQueue(this.queue) .setStatus(this.status); - - if (this.owner != null) { - j.setOwnerName(this.owner.getName()); + if (this.ownerId != null) { + nj.setOwnerName("ownerId=" + ownerId.toString()); } - return j; + return nj; } public byte[] getInput() { @@ -93,7 +88,7 @@ public Integer getJobId() { return this.jobid; } - public Date getJobdate() { + public Date getJobDate() { return jobdate; } @@ -105,8 +100,8 @@ public byte[] getOutput() { return this.output; } - public User getOwner() { - return this.owner; + public Integer getOwnerId() { + return this.ownerId; } public String getQueue() { @@ -117,44 +112,44 @@ public JobStatus getStatus() { return this.status; } - public Job setInput(byte[] input) { + public T setInput(byte[] input) { this.input = input; - return this; + return (T) this; } - public Job setJobDate(Date jobdate) { + public T setJobDate(Date jobdate) { this.jobdate = jobdate; - return this; + return (T) this; } - public Job setJobId(Integer jobid) { + public T setJobId(Integer jobid) { this.jobid = jobid; - return this; + return (T) this; } - public Job setJobType(JobType jobtype) { + public T setJobType(JobType jobtype) { this.jobtype = jobtype; - return this; + return (T) this; } - public Job setOutput(byte[] output) { + public T setOutput(byte[] output) { this.output = output; - return this; + return (T) this; } - public Job setOwner(User owner) { - this.owner = owner; - return this; + public T setOwnerId(Integer o) { + this.ownerId = o; + return (T) this; } - public Job setQueue(String queue) { + public T setQueue(String queue) { this.queue = queue; - return this; + return (T) this; } - public Job setStatus(JobStatus status) { + public T setStatus(JobStatus status) { this.status = status; - return this; + return (T) this; } /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/JobEntity.java b/crimsy-job-storage/src/main/java/de/ipb_halle/job/JobEntity.java similarity index 89% rename from ui/src/main/java/de/ipb_halle/lbac/device/job/JobEntity.java rename to crimsy-job-storage/src/main/java/de/ipb_halle/job/JobEntity.java index 945fb9c0f..efa3b290a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/job/JobEntity.java +++ b/crimsy-job-storage/src/main/java/de/ipb_halle/job/JobEntity.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; import java.io.Serializable; import java.util.Date; @@ -91,6 +91,18 @@ public class JobEntity implements Serializable { @Enumerated(EnumType.ORDINAL) private JobStatus status; + public NetJob createNetJob() { + NetJob j = new NetJob() + .setInput(this.input) + .setJobDate(this.jobdate) + .setJobId(this.jobid) + .setJobType(this.jobtype) + .setOutput(this.output) + .setQueue(this.queue) + .setStatus(this.status); + return j; + } + public byte[] getInput() { return this.input; } @@ -148,7 +160,7 @@ public JobEntity setOutput(byte[] output) { return this; } - JobEntity setOwnerId(Integer ownerid) { + public JobEntity setOwnerId(Integer ownerid) { this.ownerid = ownerid; return this; } diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/JobService.java b/crimsy-job-storage/src/main/java/de/ipb_halle/job/JobService.java similarity index 70% rename from ui/src/main/java/de/ipb_halle/lbac/device/job/JobService.java rename to crimsy-job-storage/src/main/java/de/ipb_halle/job/JobService.java index 90674a06f..36bc5a38b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/job/JobService.java +++ b/crimsy-job-storage/src/main/java/de/ipb_halle/job/JobService.java @@ -15,9 +15,8 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; -import de.ipb_halle.lbac.admission.MemberService; import java.io.Serializable; import java.util.ArrayList; @@ -36,40 +35,52 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** - * JobService loads, stores and deletes jobs. + * JobService loads, stores and deletes job entities. */ -@Stateless -public class JobService implements Serializable { +public abstract class JobService implements Serializable { private static final long serialVersionUID = 1L; + public final static String SETTING_JOB_SECRET = "SETTING_JOB_SECRET"; + public final static String JOB_SECRET_QUERY = "SELECT value FROM info WHERE key='SETTING_JOB_SECRET'"; + public final static String CONDITION_JOBTYPE = "JOBTYPE"; public final static String CONDITION_QUEUE = "QUEUE"; public final static String CONDITION_STATUS = "STATUS"; public final static String CONDITION_OWNERID = "OWNERID"; - @Inject - private MemberService memberService; - @PersistenceContext(name = "de.ipb_halle.lbac") private EntityManager em; - private Logger logger = LogManager.getLogger(this.getClass().getName());; + private Logger logger = LoggerFactory.getLogger(this.getClass());; @PostConstruct - public void JobServiceInit() { + private void JobServiceInit() { if (em == null) { logger.error("Injection failed for EntityManager. @PersistenceContext(name = \"de.ipb_halle.lbac\")"); } } + /** + * Obtain the job secret from the database + */ + public String obtainJobSecret() { + try { + return (String) em.createNativeQuery(JOB_SECRET_QUERY).getSingleResult(); + } catch (Exception e) { + // ignore + } + return null; + } + /** * load all jobs */ - public List loadAllJobs() { + public List loadAllJobs() { return loadJobs(new HashMap()); } @@ -79,7 +90,7 @@ public List loadAllJobs() { * @param cmap map of conditions * @return selected jobs */ - public List loadJobs(Map cmap) { + public List loadJobs(Map cmap) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery criteriaQuery = builder.createQuery(JobEntity.class); Root jobRoot = criteriaQuery.from(JobEntity.class); @@ -90,7 +101,23 @@ public List loadJobs(Map cmap) { return getResultsFromQuery(criteriaQuery); } - private List buildPredicatesFromConditions(Map cmap, CriteriaBuilder builder, + /** + * Builds a Job object from a JobEntity. Can be overwritten by subclasses + * to better handle the specific needs of the subclass (i.e. to load the owner). + * + * @param the JobEntity + * @return the Job object + */ + + protected abstract T buildJob(JobEntity entity); +/* { + if (entity != null) { + return new Job(entity); + } + return null; + } +*/ + protected List buildPredicatesFromConditions(Map cmap, CriteriaBuilder builder, Root jobRoot) { List predicates = new ArrayList<>(); if (cmap.get(CONDITION_JOBTYPE) != null) { @@ -111,10 +138,10 @@ private List buildPredicatesFromConditions(Map cmap, return predicates; } - private List getResultsFromQuery(CriteriaQuery criteriaQuery) { - List result = new ArrayList<>(); + private List getResultsFromQuery(CriteriaQuery criteriaQuery) { + List result = new ArrayList<>(); for (JobEntity e : em.createQuery(criteriaQuery).getResultList()) { - result.add(new Job(e, memberService.loadUserById(e.getOwnerId()))); + result.add(buildJob(e)); } return result; } @@ -127,7 +154,7 @@ private List getResultsFromQuery(CriteriaQuery criteriaQuery) { * @param cmap map of conditions * @return selected jobs */ - public List loadJobsOlderThan(Date date, Map cmap) { + public List loadJobsOlderThan(Date date, Map cmap) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery criteriaQuery = builder.createQuery(JobEntity.class); Root jobRoot = criteriaQuery.from(JobEntity.class); @@ -143,15 +170,11 @@ public List loadJobsOlderThan(Date date, Map cmap) { /** * load a Job by id * - * @param id Job Id + * @param id JobEntity Id * @return the Job object or null in case the entity does not exist */ - public Job loadJobById(Integer id) { - JobEntity entity = em.find(JobEntity.class, id); - if (entity == null) { - return null; - } - return new Job(entity, memberService.loadUserById(entity.getOwnerId())); + public T loadJobById(Integer id) { + return buildJob(em.find(JobEntity.class, id)); } /** @@ -182,10 +205,14 @@ public void removeJob(Integer id) { /** * save a single job object * - * @param job the Job to save - * @return the persisted Job DTO + * @param job the JobEntity to save + * @return the Job */ - public Job saveJob(Job job) { - return new Job(em.merge(job.createEntity()), job.getOwner()); + public T saveJob(T job) { + return buildJob(saveEntity(job)); + } + + protected JobEntity saveEntity(Job job) { + return em.merge(job.createEntity()); } } diff --git a/crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceImpl.java b/crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceImpl.java new file mode 100644 index 000000000..478815447 --- /dev/null +++ b/crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceImpl.java @@ -0,0 +1,32 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.job; + +import java.io.Serializable; +import javax.ejb.Stateless; + +@Stateless +public class JobServiceImpl extends JobService implements Serializable { + + protected TestJob buildJob(JobEntity e) { + if (e != null) { + return new TestJob(e); + } + return null; + } +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/device/job/JobServiceTest.java b/crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceTest.java similarity index 62% rename from ui/src/test/java/de/ipb_halle/lbac/device/job/JobServiceTest.java rename to crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceTest.java index d08bc18e1..e7ed0d5da 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/device/job/JobServiceTest.java +++ b/crimsy-job-storage/src/test/java/de/ipb_halle/job/JobServiceTest.java @@ -15,12 +15,13 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.device.job; +package de.ipb_halle.job; -import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; -import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; -import static de.ipb_halle.lbac.device.job.JobType.COMPUTE; -import static de.ipb_halle.lbac.device.job.JobType.PRINT; +import de.ipb_halle.test.EntityManagerService; +import static de.ipb_halle.job.JobStatus.BUSY; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobType.COMPUTE; +import static de.ipb_halle.job.JobType.PRINT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertArrayEquals; @@ -30,6 +31,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -40,10 +42,12 @@ import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** @@ -51,31 +55,47 @@ */ @ExtendWith(PostgresqlContainerExtension.class) @ExtendWith(ArquillianExtension.class) -public class JobServiceTest extends TestBase { +public class JobServiceTest { private static final long serialVersionUID = 1L; + private final static int publicUser = 2; + private final static int adminUser = 4; @Inject - private JobService jobService; + private JobServiceImpl jobService; + + @Inject + private EntityManagerService entityManagerService; @Deployment public static WebArchive createDeployment() { - return prepareDeployment("JobServiceTest.war").addClass(JobService.class); + WebArchive archive = ShrinkWrap.create(WebArchive.class, "JobServiceTest.war") + .addClass(JobServiceImpl.class) + .addClass(EntityManagerService.class) + .addAsResource("PostgresqlContainerSchemaFiles") + .addAsWebInfResource("test-persistence.xml", "persistence.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; + } + + @BeforeEach + public void before() { + entityManagerService.doSqlUpdate("DELETE FROM jobs"); } @Test public void test_saveJob_and_loadAllJobs() { - Job originalJob1 = new Job().setInput("abc".getBytes()).setJobDate(new Date(100)).setJobType(PRINT) - .setOutput("def".getBytes()).setOwner(publicUser).setQueue("queue1").setStatus(PENDING); - Job originalJob2 = new Job().setInput("ghi".getBytes()).setJobDate(new Date(200)).setJobType(COMPUTE) - .setOutput("jkl".getBytes()).setOwner(adminUser).setQueue("queue2").setStatus(BUSY); + TestJob originalJob1 = new TestJob().setOwnerId(publicUser).setInput("abc".getBytes()).setJobDate(new Date(100)) + .setJobType(PRINT).setOutput("def".getBytes()).setQueue("queue1").setStatus(PENDING); + TestJob originalJob2 = new TestJob().setOwnerId(adminUser).setInput("ghi".getBytes()).setJobDate(new Date(200)) + .setJobType(COMPUTE).setOutput("jkl".getBytes()).setQueue("queue2").setStatus(BUSY); - Job savedJob1 = jobService.saveJob(originalJob1); - Job savedJob2 = jobService.saveJob(originalJob2); + TestJob savedJob1 = jobService.saveJob(originalJob1); + TestJob savedJob2 = jobService.saveJob(originalJob2); - List loadedJobs = jobService.loadAllJobs(); + List loadedJobs = jobService.loadAllJobs(); assertThat(loadedJobs, hasSize(2)); - Job loadedJob1 = loadedJobs.get(0); - Job loadedJob2 = loadedJobs.get(1); + TestJob loadedJob1 = loadedJobs.get(0); + TestJob loadedJob2 = loadedJobs.get(1); assertJobsEqual(originalJob1, savedJob1); assertJobsEqual(originalJob2, savedJob2); @@ -87,8 +107,8 @@ public void test_saveJob_and_loadAllJobs() { @Test public void test_loadJobs_withConditions() { - Job job1 = new Job().setJobType(PRINT).setStatus(PENDING).setOwner(adminUser).setQueue("queue1"); - Job job2 = new Job().setJobType(PRINT).setStatus(BUSY).setOwner(adminUser).setQueue("queue1"); + TestJob job1 = new TestJob().setOwnerId(adminUser).setJobType(PRINT).setStatus(PENDING).setQueue("queue1"); + TestJob job2 = new TestJob().setOwnerId(adminUser).setJobType(PRINT).setStatus(BUSY).setQueue("queue1"); job1 = jobService.saveJob(job1); job2 = jobService.saveJob(job2); @@ -96,9 +116,9 @@ public void test_loadJobs_withConditions() { conditions.put(JobService.CONDITION_JOBTYPE, PRINT); conditions.put(JobService.CONDITION_QUEUE, "queue1"); conditions.put(JobService.CONDITION_STATUS, BUSY); - conditions.put(JobService.CONDITION_OWNERID, adminUser.getId()); + conditions.put(JobService.CONDITION_OWNERID, adminUser); - List loadedJobs = jobService.loadJobs(conditions); + List loadedJobs = jobService.loadJobs(conditions); assertThat(loadedJobs, hasSize(1)); assertEquals(job2.getJobId(), loadedJobs.get(0).getJobId()); @@ -113,7 +133,7 @@ public void test_loadJobsOlderThan() { Date twoDaysAgo = Date.from(now.minus(Duration.ofDays(2))); Date threeDaysAgo = Date.from(now.minus(Duration.ofDays(3))); - Job job = new Job().setJobDate(twoDaysAgo).setJobType(PRINT).setStatus(PENDING).setOwner(adminUser) + TestJob job = new TestJob().setOwnerId(adminUser).setJobDate(twoDaysAgo).setJobType(PRINT).setStatus(PENDING) .setQueue(""); job = jobService.saveJob(job); @@ -125,10 +145,10 @@ public void test_loadJobsOlderThan() { @Test public void test_loadJobById() { - Job job = new Job().setJobType(PRINT).setStatus(PENDING).setOwner(adminUser).setQueue("queue1"); + TestJob job = new TestJob().setOwnerId(adminUser).setJobType(PRINT).setStatus(PENDING).setQueue("queue1"); job = jobService.saveJob(job); - Job loadedJob = jobService.loadJobById(job.getJobId()); + TestJob loadedJob = jobService.loadJobById(job.getJobId()); assertEquals(job.getJobId(), loadedJob.getJobId()); assertJobsEqual(job, loadedJob); @@ -138,19 +158,17 @@ public void test_loadJobById() { @Test public void test_removeJob_withJob() { - Job job = new Job().setJobType(PRINT).setStatus(PENDING).setOwner(adminUser).setQueue("queue1"); + TestJob job = new TestJob().setOwnerId(adminUser).setJobType(PRINT).setStatus(PENDING).setQueue("queue1"); job = jobService.saveJob(job); jobService.removeJob(job); assertThat(jobService.loadAllJobs(), hasSize(0)); - - jobService.removeJob(job); } @Test public void test_removeJob_withId() { - Job job = new Job().setJobType(PRINT).setStatus(PENDING).setOwner(adminUser).setQueue("queue1"); + TestJob job = new TestJob().setOwnerId(adminUser).setJobType(PRINT).setStatus(PENDING).setQueue("queue1"); job = jobService.saveJob(job); jobService.removeJob((Integer) null); @@ -164,11 +182,12 @@ public void test_removeJob_withId() { private void assertJobsEqual(Job job1, Job job2) { assertArrayEquals(job1.getInput(), job2.getInput()); - assertEquals(job1.getJobdate(), job2.getJobdate()); + assertEquals(job1.getJobDate(), job2.getJobDate()); assertEquals(job1.getJobType(), job2.getJobType()); assertArrayEquals(job1.getOutput(), job2.getOutput()); - assertTrue(job1.getOwner().isEqualTo(job2.getOwner())); assertEquals(job1.getQueue(), job2.getQueue()); assertEquals(job1.getStatus(), job2.getStatus()); + + assertTrue(job1.getOwnerId() == job2.getOwnerId()); } -} \ No newline at end of file +} diff --git a/crimsy-job-storage/src/test/java/de/ipb_halle/job/TestJob.java b/crimsy-job-storage/src/test/java/de/ipb_halle/job/TestJob.java new file mode 100644 index 000000000..7224f333b --- /dev/null +++ b/crimsy-job-storage/src/test/java/de/ipb_halle/job/TestJob.java @@ -0,0 +1,29 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.job; + +public class TestJob extends Job { + + public TestJob() { + super(); + } + + public TestJob(JobEntity e) { + super(e); + } +} diff --git a/crimsy-job-storage/src/test/resources/PostgresqlContainerSchemaFiles b/crimsy-job-storage/src/test/resources/PostgresqlContainerSchemaFiles new file mode 100644 index 000000000..8fc81a1ec --- /dev/null +++ b/crimsy-job-storage/src/test/resources/PostgresqlContainerSchemaFiles @@ -0,0 +1,5 @@ +# +# Configuration of PostgresqlContainerExtension: +# schema files for testing +# +00001.sql diff --git a/crimsy-job-storage/src/test/resources/arquillian.xml b/crimsy-job-storage/src/test/resources/arquillian.xml new file mode 100644 index 000000000..707677827 --- /dev/null +++ b/crimsy-job-storage/src/test/resources/arquillian.xml @@ -0,0 +1,49 @@ + + + + + + + 8800 + + + + target/arquillian + + apiDS = new://Resource?type=DataSource + apiDS.JdbcDriver = org.postgresql.Driver + apiDS.JdbcUrl = jdbc:postgresql://localhost:65432/lbac?charSet=UTF-8 + apiDS.UserName = lbac + apiDS.Password = lbac + apiDS.JtaManaged = true + apiDS.LogSql = true + # + # + tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar + tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ + postgresql*.jar,\ + log4j*.jar,slf4j*.jar,hibernate*.jar + + true + + + + + + + diff --git a/crimsy-job-storage/src/test/resources/schema/00001.sql b/crimsy-job-storage/src/test/resources/schema/00001.sql new file mode 100644 index 000000000..6b2a8846c --- /dev/null +++ b/crimsy-job-storage/src/test/resources/schema/00001.sql @@ -0,0 +1,113 @@ +/* + * Leibniz Bioactives Cloud + * Init script for database postgres 12.6 + * + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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. + * + *========================================================= + * + * NOTE: TABLE CONSTRAINTS IN THIS FILE ARE RELAXED FOR TESTING PURPOSES: + * + * - collection_id may be NULL in table 'files' + * - user_id may be NULL in table 'files' + * + *========================================================= + * + * define global vars + */ +\set LBAC_SCHEMA_VERSION '\'00001\'' + +\set LBAC_SCHEMA lbac +\set LBAC_DATABASE lbac +\set LBAC_USER lbac +\set LBAC_PW lbac +-- quoted stuff -- +\set LBAC_SCHEMA_QUOTED '\'' :LBAC_SCHEMA '\'' +\set LBAC_DATABASE_QUOTED '\'' :LBAC_DATABASE '\'' +\set LBAC_USER_QUOTED '\'' :LBAC_USER '\'' +\set LBAC_PW_QUOTED '\'' :LBAC_PW '\'' + +/* + *========================================================= + * + * terminate all session from database lbac -- + * getting db exclusive -- + */ +SELECT pg_terminate_backend(pg_stat_activity.pid) +FROM pg_stat_activity +WHERE pg_stat_activity.datname = :LBAC_DATABASE_QUOTED + AND pid <> pg_backend_pid(); + +/* + * clean up + */ +-- the following statement fails if LBAC_USER is not known! +-- REASSIGN OWNED BY :LBAC_USER TO postgres; +DROP SCHEMA IF EXISTS :LBAC_SCHEMA CASCADE; +DROP DATABASE IF EXISTS :LBAC_DATABASE; +DROP USER IF EXISTS :LBAC_USER; + +/* + * (re-)create database objects + */ +-- roles -- +CREATE USER :LBAC_USER PASSWORD :LBAC_PW_QUOTED; +-- db -- +CREATE DATABASE :LBAC_DATABASE WITH ENCODING 'UTF8' OWNER :LBAC_USER; + +\connect :LBAC_DATABASE + +-- schema -- +CREATE SCHEMA AUTHORIZATION :LBAC_USER; + +-- adjust schema search path -- +ALTER USER :LBAC_USER SET search_path to :LBAC_SCHEMA,public; + +-- privileges -- +GRANT USAGE ON SCHEMA :LBAC_SCHEMA, public TO :LBAC_USER; +GRANT CONNECT, TEMPORARY, TEMP ON DATABASE :LBAC_DATABASE to :LBAC_USER; +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA :LBAC_SCHEMA to :LBAC_USER; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to :LBAC_USER; +REVOKE ALL ON ALL TABLES IN SCHEMA :LBAC_SCHEMA FROM public; + +\connect - :LBAC_USER + +BEGIN TRANSACTION; + +-- tables -- + +CREATE TABLE info ( + key VARCHAR NOT NULL PRIMARY KEY, + value VARCHAR, + owner_id INTEGER, + aclist_id INTEGER +); +INSERT INTO info VALUES ('SETTING_JOB_SECRET', 'test_secret', null, null); + +/** + * - CRIMSy Jobs + */ +CREATE TABLE jobs ( + jobid SERIAL NOT NULL PRIMARY KEY, + input BYTEA, + jobdate TIMESTAMP NOT NULL DEFAULT now(), + jobtype INTEGER NOT NULL, + output BYTEA, + ownerid INTEGER, + queue VARCHAR NOT NULL, + status INTEGER NOT NULL +); + +COMMIT; diff --git a/crimsy-job-storage/src/test/resources/test-persistence.xml b/crimsy-job-storage/src/test/resources/test-persistence.xml new file mode 100644 index 000000000..af3198f53 --- /dev/null +++ b/crimsy-job-storage/src/test/resources/test-persistence.xml @@ -0,0 +1,54 @@ + + + + + + + + LBAC api + apiDS + + org.hibernate.jpa.HibernatePersistenceProvider + + de.ipb_halle.job.JobEntity + + true + + + + + + + + + + + + + + + + + diff --git a/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java b/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java index fd2bfc794..95692731a 100644 --- a/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java +++ b/crimsy-test/src/main/java/de/ipb_halle/test/EntityManagerService.java @@ -18,8 +18,10 @@ package de.ipb_halle.test; import java.util.List; +import java.util.Map; import javax.ejb.Stateless; import javax.persistence.EntityManager; +import javax.persistence.Query; import javax.persistence.PersistenceContext; /** @@ -32,17 +34,34 @@ public class EntityManagerService { @PersistenceContext(name = "de.ipb_halle.lbac") private EntityManager em; - public EntityManager getEntityManager() { - return em; + @SuppressWarnings("unchecked") + public List doSqlQuery(String query) { + return em.createNativeQuery(query).getResultList(); } public void doSqlUpdate(String query) { em.createNativeQuery(query).executeUpdate(); } - @SuppressWarnings("unchecked") - public List doSqlQuery(String query) { - return em.createNativeQuery(query).getResultList(); + public void doSqlUpdate(String queryString, Map param) { + Query query = em.createNativeQuery(queryString); + for (Map.Entry entry : param.entrySet()) { + query.setParameter(entry.getKey(), entry.getValue()); + } + query.executeUpdate(); } + public void flush() { + this.em.flush(); + } + + public EntityManager getEntityManager() { + return em; + } + + @SuppressWarnings("unchecked") + public void removeEntity(Class clazz, Object id) { + this.em.remove(this.em.find(clazz, id)); + + } } diff --git a/pom.xml b/pom.xml index 60d41c5a8..fe150ba71 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,8 @@ crimsy-test crimsy-api - agency-api + crimsy-job-api + crimsy-job-storage agency snowball tx diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/report/Report.java b/reporting-api/src/main/java/de/ipb_halle/reporting/Report.java similarity index 97% rename from reporting-api/src/main/java/de/ipb_halle/reporting/report/Report.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/Report.java index 5dd36991c..7c253487b 100644 --- a/reporting-api/src/main/java/de/ipb_halle/reporting/report/Report.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/Report.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; import de.ipb_halle.crimsy_api.DTO; diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/ReportDataPojo.java b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportDataPojo.java new file mode 100644 index 000000000..016bb2c0b --- /dev/null +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportDataPojo.java @@ -0,0 +1,89 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.reporting; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + + +/** + * Serializable data class to be used as input data of reporting jobs. + * + * @author flange + */ +public class ReportDataPojo implements Serializable { + private static final long serialVersionUID = 1L; + + private String reportURI; + private ReportType type; + private Map parameters; + + public ReportDataPojo(String reportURI, ReportType type, Map parameters) { + this.reportURI = reportURI; + this.type = type; + this.parameters = parameters; + } + + public String getReportURI() { + return reportURI; + } + + public ReportType getType() { + return type; + } + + public Map getParameters() { + return parameters; + } + + public byte[] serialize() { + byte[] bytes; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(this); + oos.flush(); + bytes = baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return bytes; + } + + public static ReportDataPojo deserialize(byte[] bytes) { + if (bytes == null) { + return null; + } + + Object o = null; + try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bais)) { + o = ois.readObject(); + if (o instanceof ReportDataPojo) { + return (ReportDataPojo) o; + } + throw new RuntimeException("deserialize(): illegal object type"); + } catch (ClassNotFoundException | IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportEntity.java b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportEntity.java similarity index 98% rename from reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportEntity.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/ReportEntity.java index 816f4bf7b..24cd41177 100644 --- a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportEntity.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportEntity.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportService.java b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportService.java similarity index 98% rename from reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportService.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/ReportService.java index 1a7dd73a8..52e89815f 100644 --- a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportService.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportService.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; import java.util.ArrayList; import java.util.List; diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/ReportType.java b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportType.java new file mode 100644 index 000000000..2f1a62ff3 --- /dev/null +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportType.java @@ -0,0 +1,39 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.reporting; + +/** + * Report types + * + * @author fbroda + */ +public enum ReportType { + PDF(".pdf"), + XLSX(".xlsx"), + CSV(".csv"); + + private final String fileExtension; + + private ReportType(String fileExtension) { + this.fileExtension = fileExtension; + } + + public String getFileExtension() { + return fileExtension; + } +} diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportsDirectory.java similarity index 96% rename from reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java rename to reporting-api/src/main/java/de/ipb_halle/reporting/ReportsDirectory.java index cab295c9c..745f58d9e 100644 --- a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportsDirectory.java +++ b/reporting-api/src/main/java/de/ipb_halle/reporting/ReportsDirectory.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; import java.io.Serializable; import javax.ejb.Singleton; diff --git a/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportServiceTest.java b/reporting-api/src/test/java/de/ipb_halle/reporting/ReportServiceTest.java similarity index 98% rename from reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportServiceTest.java rename to reporting-api/src/test/java/de/ipb_halle/reporting/ReportServiceTest.java index 9f9ef0493..27a7c47f9 100644 --- a/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportServiceTest.java +++ b/reporting-api/src/test/java/de/ipb_halle/reporting/ReportServiceTest.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; diff --git a/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportTest.java b/reporting-api/src/test/java/de/ipb_halle/reporting/ReportTest.java similarity index 97% rename from reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportTest.java rename to reporting-api/src/test/java/de/ipb_halle/reporting/ReportTest.java index e8f9fe6ea..34ab84f2e 100644 --- a/reporting-api/src/test/java/de/ipb_halle/reporting/report/ReportTest.java +++ b/reporting-api/src/test/java/de/ipb_halle/reporting/ReportTest.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; import static org.junit.Assert.assertEquals; import org.junit.jupiter.api.Test; diff --git a/reporting-api/src/test/resources/test-persistence.xml b/reporting-api/src/test/resources/test-persistence.xml index bd80738b5..e2dbfb41c 100644 --- a/reporting-api/src/test/resources/test-persistence.xml +++ b/reporting-api/src/test/resources/test-persistence.xml @@ -30,7 +30,7 @@ org.hibernate.jpa.HibernatePersistenceProvider - de.ipb_halle.reporting.report.ReportEntity + de.ipb_halle.reporting.ReportEntity true diff --git a/reporting/pom.xml b/reporting/pom.xml new file mode 100644 index 000000000..8b3e81c90 --- /dev/null +++ b/reporting/pom.xml @@ -0,0 +1,403 @@ + + + 4.0.0 + + de.ipb-halle + reporting + 1.0.0 + + reporting + http://www.ipb-halle.de + + + scm:git:https://github.com/ipb-halle/CRIMSy.git + https://github.com/ipb-halle/CRIMSy.git + + war + + + + + UTF-8 + + 5.3.26.Final + 9.2.0.4-591 + + + + + + central + https://repo.maven.apache.org/maven2/ + + + sonatype public + https://oss.sonatype.org/content/groups/public/ + + + pentaho-releases + + + https://repo.orl.eng.hitachivantara.com/artifactory/pnt-mvn + + true + true + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + -Xlint:all + ${project.build.sourceEncoding} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + random + 240 + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + validate + + create + + + + + true + true + 8 + + {0} KX (git-sha1:{2} * {1,date,yyyy-MM-dd HH:mm:ss}) + + ${project.version} + timestamp + scmVersion + + + + + + + + + maven-war-plugin + 3.3.2 + + false + reporting + + + true + + + ${buildNumber} + + + + + + + + + + + + + + de.ipb-halle + crimsy-job-api + 1.0 + + + + de.ipb-halle + crimsy-job-storage + 1.0 + + + + + commons-cli + commons-cli + 1.3.1 + + + + + org.apache.tomee + javaee-api + 7.0-2 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + + + org.apache.commons + commons-csv + 1.5 + + + + + de.ipb-halle + reporting-api + 1.0.0 + + + + + org.pentaho.reporting.engine + classic-core + ${pentaho.version} + + + + org.pentaho.reporting.engine + classic-extensions + ${pentaho.version} + + + + org.pentaho.reporting.engine + wizard-core + ${pentaho.version} + + + + org.pentaho.reporting.library + libfonts + ${pentaho.version} + pom + + + + org.pentaho.reporting.library + libloader + ${pentaho.version} + pom + + + + org.pentaho.reporting + pentaho-reporting + ${pentaho.version} + pom + + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-ehcache + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-core + ${hibernate.version} + provided + + + + + org.hibernate + hibernate-validator + 5.3.6.Final + provided + + + + + javax.persistence + javax.persistence-api + 2.2 + provided + + + + + org.postgresql + postgresql + 42.4.3 + + + + + org.glassfish + javax.el + 3.0.1-b12 + test + + + + + + + de.ipb-halle + crimsy-test + 1.0.0 + test + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + test + + + + + org.testcontainers + testcontainers + 1.16.3 + test + + + org.testcontainers + postgresql + 1.16.3 + test + + + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha6 + test + + + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.6 + pom + test + + + + + org.apache.tomee + arquillian-tomee-embedded + 7.0.9 + test + + + + org.awaitility + awaitility + 4.0.3 + test + + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + commons-io + commons-io + + + + + + diff --git a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportBuilder.java similarity index 50% rename from reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java rename to reporting/src/main/java/de/ipb_halle/reporting/ReportBuilder.java index 7989d6330..0c4255b5d 100644 --- a/reporting-api/src/main/java/de/ipb_halle/reporting/report/ReportType.java +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportBuilder.java @@ -1,6 +1,6 @@ /* * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,59 +15,43 @@ * limitations under the License. * */ -package de.ipb_halle.reporting.report; +package de.ipb_halle.reporting; +import de.ipb_halle.reporting.ReportType; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil; import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVReportUtil; import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelReportUtil; + /** - * Report types - * + * Builder for different report types (PDF, XLSX, CSV). + * * @author fbroda */ -public enum ReportType { - PDF(".pdf") { - @Override - public void createReport(MasterReport report, String filename) throws Exception { - PdfReportUtil.createPDF(report, filename); - } - }, -/* - * NOTE: disabled due to conflicting Apache POI releases - * between org.pentaho and de.ipb_halle.tx - * - XLSX(".xlsx") { - @Override - public void createReport(MasterReport report, String filename) throws Exception { - ExcelReportUtil.createXLSX(report, filename); - } - }, -*/ - CSV(".csv") { - @Override - public void createReport(MasterReport report, String filename) throws Exception { - CSVReportUtil.createCSV(report, filename, null); - } - }; - - private final String fileExtension; - - private ReportType(String fileExtension) { - this.fileExtension = fileExtension; - } - - public String getFileExtension() { - return fileExtension; - } +public class ReportBuilder { /** * Create a report and write it to the specified file * * @param report * @param filename + * @param report type * @throws Exception */ - public abstract void createReport(MasterReport report, String filename) throws Exception; + public static void createReport(MasterReport report, String filename, ReportType reportType) throws Exception { + switch(reportType) { + case PDF: + PdfReportUtil.createPDF(report, filename); + break; + case XLSX: + ExcelReportUtil.createXLSX(report, filename); + break; + case CSV: + CSVReportUtil.createCSV(report, filename, null); + break; + default: + throw new IllegalArgumentException("Invalid report type"); + } + } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportSchedulingService.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java similarity index 85% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportSchedulingService.java rename to reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java index 751aeea77..55e672b4d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportSchedulingService.java +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.job; +package de.ipb_halle.reporting; import javax.annotation.PostConstruct; import javax.ejb.DependsOn; @@ -31,10 +31,10 @@ */ @Singleton @Startup -@DependsOn("globalAdmissionContext") public class ReportSchedulingService { + @Inject - private ReportJobService reportJobService; + private ReportingJobService reportJobService; /** * Marks all busy reporting jobs as pending to ensure that previously unfinished @@ -45,15 +45,15 @@ public void startUp() { reportJobService.markBusyJobsAsPending(); // Could cause heavy load on application startup? -// reportJobService.submitPendingTasksToExecutor(); +// reportJobService.submitPendingJobsToExecutor(); } /** * Frequently submits pending reporting jobs to the ManagedExecutorService. */ @Schedule(second = "0", minute = "*", hour = "*") - public void submitPendingTasksToExecutor() { - reportJobService.submitPendingTasksToExecutor(); + public void submitPendingJobsToExecutor() { + reportJobService.submitPendingJobsToExecutor(); } /** @@ -63,4 +63,4 @@ public void submitPendingTasksToExecutor() { public void cleanUpOldJobsAndReportFiles() { reportJobService.cleanUpOldJobsAndReportFiles(); } -} \ No newline at end of file +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportTask.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportTask.java similarity index 81% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportTask.java rename to reporting/src/main/java/de/ipb_halle/reporting/ReportTask.java index 86dc9922b..2783fa4d9 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportTask.java +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportTask.java @@ -15,8 +15,9 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.job; +package de.ipb_halle.reporting; +import de.ipb_halle.reporting.ReportDataPojo; import java.io.File; import java.io.IOException; import java.net.URL; @@ -32,7 +33,6 @@ import javax.enterprise.concurrent.ManagedTaskListener; import javax.enterprise.inject.spi.CDI; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot; @@ -48,12 +48,12 @@ */ public class ReportTask implements Runnable, ManagedTask, ManagedTaskListener { private Logger logger = LogManager.getLogger(getClass().getName()); - private final ReportJobPojo reportJobPojo; + private final ReportDataPojo reportDataPojo; private final String reportsDir; private final int jobId; - public ReportTask(ReportJobPojo reportJobPojo, String reportsDir, int jobId) { - this.reportJobPojo = reportJobPojo; + public ReportTask(ReportDataPojo reportDataPojo, String reportsDir, int jobId) { + this.reportDataPojo = reportDataPojo; this.reportsDir = reportsDir; this.jobId = jobId; } @@ -68,9 +68,9 @@ public void run() { String reportFilePath = null; try { - reportFile = File.createTempFile("report", reportJobPojo.getType().getFileExtension(), getReportDir()); + reportFile = File.createTempFile("report", reportDataPojo.getType().getFileExtension(), getReportDir()); reportFilePath = reportFile.getAbsolutePath(); - URL url = new URL(reportJobPojo.getReportURI()); + URL url = new URL(reportDataPojo.getReportURI()); ClassicEngineBoot.getInstance().start(); ResourceManager manager = new ResourceManager(); @@ -78,7 +78,7 @@ public void run() { Resource resource = manager.createDirectly(url, MasterReport.class); MasterReport report = (MasterReport) resource.getResource(); - for (Entry entry : reportJobPojo.getParameters().entrySet()) { + for (Entry entry : reportDataPojo.getParameters().entrySet()) { String paramName = entry.getKey(); Object paramValue = entry.getValue(); @@ -87,7 +87,7 @@ public void run() { // This circumvents Pentaho's caching. report.getParameterValues().put("paramRandom", new Random().nextLong()); - reportJobPojo.getType().createReport(report, reportFilePath); + ReportBuilder.createReport(report, reportFilePath, reportDataPojo.getType()); } catch (Exception e) { if ((reportFile != null) && (reportFile.exists())) { reportFile.delete(); @@ -106,16 +106,16 @@ private File getReportDir() throws IOException { } private void done(String reportFilePath) { - reportJobService().markJobAsCompleted(jobId, reportFilePath); + reportingJobService().markJobAsCompleted(jobId, reportFilePath); } private void fail(Throwable exception) { - logger.warn("Task with jobId={} failed: {}", jobId, ExceptionUtils.getStackTrace(exception)); - reportJobService().markJobAsFailed(jobId); + logger.warn(String.format("Task with jobId=%d failed: ", jobId), (Throwable) exception); + reportingJobService().markJobAsFailed(jobId); } - private ReportJobService reportJobService() { - return CDI.current().select(ReportJobService.class).get(); + private ReportingJobService reportingJobService() { + return CDI.current().select(ReportingJobService.class).get(); } @Override @@ -148,4 +148,4 @@ public void taskAborted(Future future, ManagedExecutorService executor, Objec @Override public void taskDone(Future future, ManagedExecutorService executor, Object task, Throwable exception) { } -} \ No newline at end of file +} diff --git a/reporting/src/main/java/de/ipb_halle/reporting/ReportingJob.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportingJob.java new file mode 100644 index 000000000..a9c2581b0 --- /dev/null +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportingJob.java @@ -0,0 +1,77 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.reporting; + +import de.ipb_halle.job.Job; +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.job.JobStatus; +import de.ipb_halle.job.JobType; +import de.ipb_halle.job.NetJob; + +import java.io.File; +import java.util.Date; + +/** + * ReportingJob + * Not to be confused with ReportJob from the ui module, + * which explicitly adds a user / owner. + * + * @author fbroda + */ +public class ReportingJob extends Job { + + public ReportingJob() { + super(); + setJobType(JobType.REPORT); + } + + public ReportingJob(JobEntity entity) { + super(entity); + if (entity.getJobType() != JobType.REPORT) { + throw new IllegalArgumentException("Attempt to create PRINT job from non-report-job entity"); + } + } + + @Override + public JobEntity createEntity() { + JobEntity e = super.createEntity(); + return e; + } + + @Override + public void update(NetJob netjob) { + if (netjob.getJobType() != JobType.REPORT) { + throw new IllegalArgumentException("Attempt to update PRINT job from non-report-job NetJob"); + } + super.update(netjob); + } + + + public boolean isDeleteable() { + return isFailed() || isCompleted(); + } + + + private boolean isFailed() { + return JobStatus.FAILED.equals(getStatus()); + } + + private boolean isCompleted() { + return JobStatus.COMPLETED.equals(getStatus()); + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java similarity index 58% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java rename to reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java index 93a8b385d..0897a6c64 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobService.java +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java @@ -15,16 +15,16 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.job; - -import static de.ipb_halle.lbac.device.job.JobService.CONDITION_JOBTYPE; -import static de.ipb_halle.lbac.device.job.JobService.CONDITION_OWNERID; -import static de.ipb_halle.lbac.device.job.JobService.CONDITION_STATUS; -import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; -import static de.ipb_halle.lbac.device.job.JobStatus.COMPLETED; -import static de.ipb_halle.lbac.device.job.JobStatus.FAILED; -import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; -import static de.ipb_halle.lbac.device.job.JobType.REPORT; +package de.ipb_halle.reporting; + +import static de.ipb_halle.job.JobService.CONDITION_JOBTYPE; +import static de.ipb_halle.job.JobService.CONDITION_OWNERID; +import static de.ipb_halle.job.JobService.CONDITION_STATUS; +import static de.ipb_halle.job.JobStatus.BUSY; +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobStatus.FAILED; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobType.REPORT; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -48,18 +48,15 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import de.ipb_halle.lbac.admission.GlobalAdmissionContext; -import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; -import de.ipb_halle.reporting.report.ReportsDirectory; +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.job.JobService; /** * * @author flange */ @Stateless -public class ReportJobService { +public class ReportingJobService extends JobService { private Logger logger = LogManager.getLogger(getClass().getName()); /** @@ -74,22 +71,12 @@ public class ReportJobService { @Inject private ReportsDirectory reportsDirectory; - @Inject - private JobService jobService; - - /** - * Create a new pending reporting job and try to submit it to the - * ManagedExecutorService immediately. - * - * @param reportJobPojo - * @param owner - */ - public void submit(ReportJobPojo reportJobPojo, User owner) { - Job newJob = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(owner).setQueue("") - .setInput(serialize(reportJobPojo)); - newJob = jobService.saveJob(newJob); - submitJob(newJob); + protected ReportingJob buildJob(JobEntity e) { + if (e != null) { + return new ReportingJob(e); + } + return null; } /** @@ -97,7 +84,7 @@ public void submit(ReportJobPojo reportJobPojo, User owner) { * ensure that previously unfinished reporting jobs are restarted. */ public void markBusyJobsAsPending() { - for (Job job : busyJobs()) { + for (ReportingJob job : busyJobs()) { markJobAsPending(job); } } @@ -107,8 +94,8 @@ public void markBusyJobsAsPending() { * this activity as soon as the ManagedExecutorService cannot accept any new * tasks. */ - public void submitPendingTasksToExecutor() { - for (Job job : pendingJobs()) { + public void submitPendingJobsToExecutor() { + for (ReportingJob job : pendingJobs()) { boolean submitSuccessful = submitJob(job); if (!submitSuccessful) { break; @@ -123,14 +110,14 @@ public void submitPendingTasksToExecutor() { * @param reportFilePath * @return the job DTO */ - public Job markJobAsCompleted(int jobId, String reportFilePath) { - Job job = jobService.loadJobById(jobId); + public ReportingJob markJobAsCompleted(int jobId, String reportFilePath) { + ReportingJob job = loadJobById(jobId); if (job == null) { return null; } job.setStatus(COMPLETED).setOutput(reportFilePath.getBytes()); - return jobService.saveJob(job); + return saveJob(job); } /** @@ -139,22 +126,14 @@ public Job markJobAsCompleted(int jobId, String reportFilePath) { * @param jobId * @return the job DTO */ - public Job markJobAsFailed(int jobId) { - Job job = jobService.loadJobById(jobId); + public ReportingJob markJobAsFailed(int jobId) { + ReportingJob job = loadJobById(jobId); if (job == null) { return null; } job.setStatus(FAILED); - return jobService.saveJob(job); - } - - public List loadJobsForUser(User u) { - Map cmap = new HashMap<>(); - cmap.put(CONDITION_JOBTYPE, REPORT); - cmap.put(CONDITION_OWNERID, u.getId()); - - return jobService.loadJobs(cmap); + return saveJob(job); } /** @@ -162,8 +141,8 @@ public List loadJobsForUser(User u) { * * @param job */ - public void deleteJob(Job job) { - jobService.removeJob(job); + public void deleteJob(ReportingJob job) { + removeJob(job); byte[] output = job.getOutput(); if (output != null) { deleteFileIfExists(new String(output)); @@ -177,8 +156,8 @@ public void deleteJob(Job job) { * @return report file or null in case the report job does not exist in the * database, does not have a report file or the file does not exist */ - public File getOutputFileOfJob(Job job) { - Job jobFromDB = jobService.loadJobById(job.getJobId()); + public File getOutputFileOfJob(ReportingJob job) { + ReportingJob jobFromDB = loadJobById(job.getJobId()); if (jobFromDB == null) { return null; } @@ -201,7 +180,7 @@ public File getOutputFileOfJob(Job job) { * Removes old reporting jobs and cleans orphaned files in the report directory. */ public void cleanUpOldJobsAndReportFiles() { - for (Job job : oldJobs()) { + for (ReportingJob job : oldJobs()) { deleteJob(job); } @@ -212,14 +191,12 @@ public void cleanUpOldJobsAndReportFiles() { } } - /** - * Tries to submit a report job to the ManagedExecutorService and marks it as - * busy in case the submission was successful. - * - * @param job report job - * @return if the job could be submitted successfully - */ - private boolean submitJob(Job job) { + private ReportTask prepareTask(ReportingJob job) { + ReportDataPojo reportDataPojo = ReportDataPojo.deserialize(job.getInput()); + return new ReportTask(reportDataPojo, getReportsDirectory(), job.getJobId()); + } + + private boolean submitJob(ReportingJob job) { try { ReportTask task = prepareTask(job); managedExecutorService.submit(task); @@ -230,71 +207,40 @@ private boolean submitJob(Job job) { return true; } - private ReportTask prepareTask(Job job) { - ReportJobPojo reportJobPojo = (ReportJobPojo) deserialize(job.getInput()); - return new ReportTask(reportJobPojo, getReportsDirectory(), job.getJobId()); - } - - private String getReportsDirectory() { + // default access for testing + String getReportsDirectory() { return reportsDirectory.getReportsDirectory(); } - private byte[] serialize(Object o) { - byte[] bytes; - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos)) { - oos.writeObject(o); - oos.flush(); - bytes = baos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return bytes; - } - - private Object deserialize(byte[] bytes) { - if (bytes == null) { - return null; - } - Object o = null; - try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(bais)) { - o = ois.readObject(); - } catch (ClassNotFoundException | IOException e) { - throw new RuntimeException(e); - } - return o; - } - - private List pendingJobs() { + private List pendingJobs() { Map cmap = new HashMap<>(); cmap.put(CONDITION_JOBTYPE, REPORT); cmap.put(CONDITION_STATUS, PENDING); - return jobService.loadJobs(cmap); + return loadJobs(cmap); } - private List busyJobs() { + private List busyJobs() { Map cmap = new HashMap<>(); cmap.put(CONDITION_JOBTYPE, REPORT); cmap.put(CONDITION_STATUS, BUSY); - return jobService.loadJobs(cmap); + return loadJobs(cmap); } - private Job markJobAsPending(Job job) { + private ReportingJob markJobAsPending(ReportingJob job) { job.setStatus(PENDING); - return jobService.saveJob(job); + return saveJob(job); } - private Job markJobAsBusy(Job job) { + private ReportingJob markJobAsBusy(ReportingJob job) { job.setStatus(BUSY); - return jobService.saveJob(job); + return saveJob(job); } - private List oldJobs() { + private List oldJobs() { Map cmap = new HashMap<>(); cmap.put(CONDITION_JOBTYPE, REPORT); - return jobService.loadJobsOlderThan(Date.from(Instant.now().minus(MAX_AGE)), cmap); + return loadJobsOlderThan(Date.from(Instant.now().minus(MAX_AGE)), cmap); } private void deleteFileIfExists(String filename) { @@ -324,8 +270,11 @@ private void deleteOrphanedReportFiles() throws IOException { }); } - // Replace managedExecutorService in tests - public void setManagedExecutorService(ManagedExecutorService managedExecutorService) { + public void replaceManagedExecutorService(ManagedExecutorService managedExecutorService) { this.managedExecutorService = managedExecutorService; } + + public void replaceReportsDirectory(ReportsDirectory rp) { + this.reportsDirectory = rp; + } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java b/reporting/src/test/java/de/ipb_halle/reporting/ReportTaskTest.java similarity index 67% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java rename to reporting/src/test/java/de/ipb_halle/reporting/ReportTaskTest.java index eb3d8fa47..360c905df 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportTaskTest.java +++ b/reporting/src/test/java/de/ipb_halle/reporting/ReportTaskTest.java @@ -15,13 +15,18 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.job; - -import static de.ipb_halle.lbac.device.job.JobStatus.COMPLETED; -import static de.ipb_halle.lbac.device.job.JobStatus.FAILED; -import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; -import static de.ipb_halle.lbac.device.job.JobType.REPORT; -import static de.ipb_halle.reporting.report.ReportType.CSV; +package de.ipb_halle.reporting; + +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.reporting.ReportDataPojo; +import de.ipb_halle.reporting.ReportsDirectory; +import de.ipb_halle.test.ManagedExecutorServiceMock; + +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobStatus.FAILED; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobType.REPORT; +import static de.ipb_halle.reporting.ReportType.CSV; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -38,14 +43,14 @@ import org.apache.commons.io.FileUtils; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; -import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** @@ -53,18 +58,31 @@ */ @ExtendWith(PostgresqlContainerExtension.class) @ExtendWith(ArquillianExtension.class) -public class ReportTaskTest extends TestBase { +public class ReportTaskTest { private static final long serialVersionUID = 1L; @Inject - private JobService jobService; + private ReportingJobService jobService; @TempDir private static File tempReportsDir; @Deployment public static WebArchive createDeployment() { - return prepareDeployment("ReportTaskTest.war").addClasses(ReportJobService.class, JobService.class); + WebArchive archive = ShrinkWrap.create(WebArchive.class, "ReportTaskTest.war") + .addClass(JobEntity.class) + .addClass(ReportsDirectory.class) + .addClass(ReportingJobService.class) + .addClass(ManagedExecutorServiceMock.class) + .addAsResource("PostgresqlContainerSchemaFiles") + .addAsWebInfResource("test-persistence.xml", "persistence.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; + } + + @BeforeEach + public void init() { + jobService.replaceManagedExecutorService(new ManagedExecutorServiceMock(2)); } @Test @@ -73,13 +91,13 @@ public void test_run_generatesReport() throws IOException { Map parameters = new HashMap<>(); parameters.put("paramHello", 42); parameters.put("paramWorld", "abcdef"); - ReportJobPojo reportJobPojo = new ReportJobPojo(reportURI, CSV, parameters); + ReportDataPojo reportDataPojo = new ReportDataPojo(reportURI, CSV, parameters); int jobId = prepareJob(); - ReportTask task = new ReportTask(reportJobPojo, tempReportsDir.getAbsolutePath(), jobId); + ReportTask task = new ReportTask(reportDataPojo, tempReportsDir.getAbsolutePath(), jobId); task.run(); - Job finishedJob = jobService.loadJobById(jobId); + ReportingJob finishedJob = jobService.loadJobById(jobId); assertEquals(COMPLETED, finishedJob.getStatus()); byte[] output = finishedJob.getOutput(); @@ -100,13 +118,13 @@ public void test_run_failsDueToWrongParameterType() { Map parameters = new HashMap<>(); parameters.put("paramHello", "42"); // wrong type! parameters.put("paramWorld", "abcdef"); - ReportJobPojo reportJobPojo = new ReportJobPojo(reportURI, CSV, parameters); + ReportDataPojo reportDataPojo = new ReportDataPojo(reportURI, CSV, parameters); int jobId = prepareJob(); - ReportTask task = new ReportTask(reportJobPojo, tempReportsDir.getAbsolutePath(), jobId); + ReportTask task = new ReportTask(reportDataPojo, tempReportsDir.getAbsolutePath(), jobId); task.run(); - Job failedJob = jobService.loadJobById(jobId); + ReportingJob failedJob = jobService.loadJobById(jobId); assertEquals(FAILED, failedJob.getStatus()); assertNull(failedJob.getOutput()); @@ -119,13 +137,13 @@ public void test_run_failsDueToMissingReportTemplate() { Map parameters = new HashMap<>(); parameters.put("paramHello", 42); parameters.put("paramWorld", "abcdef"); - ReportJobPojo reportJobPojo = new ReportJobPojo(reportURI, CSV, parameters); + ReportDataPojo reportDataPojo = new ReportDataPojo(reportURI, CSV, parameters); int jobId = prepareJob(); - ReportTask task = new ReportTask(reportJobPojo, tempReportsDir.getAbsolutePath(), jobId); + ReportTask task = new ReportTask(reportDataPojo, tempReportsDir.getAbsolutePath(), jobId); task.run(); - Job failedJob = jobService.loadJobById(jobId); + ReportingJob failedJob = jobService.loadJobById(jobId); assertEquals(FAILED, failedJob.getStatus()); assertNull(failedJob.getOutput()); @@ -151,13 +169,13 @@ public void test_taskAborted() { task.taskAborted(null, null, task, new Exception()); - Job failedJob = jobService.loadJobById(jobId); + ReportingJob failedJob = jobService.loadJobById(jobId); assertEquals(FAILED, failedJob.getStatus()); assertNull(failedJob.getOutput()); } private int prepareJob() { - Job newJob = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue("").setInput(null) + ReportingJob newJob = new ReportingJob().setJobType(REPORT).setStatus(PENDING).setQueue("").setInput(null) .setOutput(null); newJob = jobService.saveJob(newJob); return newJob.getJobId(); diff --git a/reporting/src/test/java/de/ipb_halle/reporting/ReportingJobServiceTest.java b/reporting/src/test/java/de/ipb_halle/reporting/ReportingJobServiceTest.java new file mode 100644 index 000000000..9f40672a0 --- /dev/null +++ b/reporting/src/test/java/de/ipb_halle/reporting/ReportingJobServiceTest.java @@ -0,0 +1,296 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.reporting; + +import static de.ipb_halle.job.JobService.CONDITION_JOBTYPE; +import static de.ipb_halle.job.JobService.CONDITION_STATUS; +import static de.ipb_halle.job.JobStatus.BUSY; +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobStatus.FAILED; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobType.REPORT; +import static de.ipb_halle.reporting.ReportType.CSV; +import static de.ipb_halle.reporting.ReportType.PDF; +import static de.ipb_halle.reporting.ReportType.XLSX; +import static de.ipb_halle.reporting.ReportingJobService.MAX_AGE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import de.ipb_halle.job.JobService; +import de.ipb_halle.reporting.ReportDataPojo; +import de.ipb_halle.reporting.mocks.ReportsDirectoryMock; +import de.ipb_halle.test.EntityManagerService; +import de.ipb_halle.test.ManagedExecutorServiceMock; +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.enterprise.concurrent.ManagedExecutorService; +import javax.inject.Inject; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +/** + * @author flange + */ +@ExtendWith(PostgresqlContainerExtension.class) +@ExtendWith(ArquillianExtension.class) +public class ReportingJobServiceTest { + private static final long serialVersionUID = 1L; + + @Inject + private ReportingJobService jobService; + + @Inject + private ReportsDirectoryMock reportsDirectory; + + @Inject + private EntityManagerService entityManagerService; + + private ManagedExecutorServiceMock managedExecutorService; + private Integer adminUser = 4; + private Integer publicUser = 1; + + @Deployment + public static WebArchive createDeployment() { + WebArchive archive = ShrinkWrap.create(WebArchive.class, "ReportingJobServiceTest.war") + .addClass(JobService.class) + .addClass(ReportsDirectoryMock.class) + .addClass(ReportingJobService.class) + .addClass(EntityManagerService.class) + .addClass(ManagedExecutorServiceMock.class) + .addAsResource("PostgresqlContainerSchemaFiles") + .addAsWebInfResource("test-persistence.xml", "persistence.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; + } + + @BeforeEach + public void initExecutor() { + entityManagerService.doSqlUpdate("DELETE FROM jobs"); + managedExecutorService = new ManagedExecutorServiceMock(2); + jobService.replaceManagedExecutorService(managedExecutorService); + jobService.replaceReportsDirectory(reportsDirectory); + } + + + @Test + public void test_submit() { + Map params = new HashMap<>(); + params.put("abc", "def"); + + ReportingJob busy1 = submit(new ReportDataPojo("report1 (busy)", CSV, params), adminUser); + ReportingJob busy2 = submit(new ReportDataPojo("report2 (busy)", PDF, new HashMap<>()), adminUser); + jobService.submitPendingJobsToExecutor(); + + ReportingJob pending1 = submit(new ReportDataPojo("report3 (pending)", XLSX, null), adminUser); + ReportingJob pending2 = submit(new ReportDataPojo("report4 (pending)", CSV, null), adminUser); + jobService.submitPendingJobsToExecutor(); + + assertThat(managedExecutorService.getSubmittedTasks(), hasSize(2)); + + List allJobs = allJobs(); + List busyJobs = busyJobs(); + List pendingJobs = pendingJobs(); + assertThat(allJobs, hasSize(4)); + assertThat(busyJobs, hasSize(2)); + assertThat(pendingJobs, hasSize(2)); + + assertEquals(busyJobs.get(0).getJobId(), busy1.getJobId()); + ReportDataPojo pojo = ReportDataPojo.deserialize(busyJobs.get(0).getInput()); + assertEquals("report1 (busy)", pojo.getReportURI()); + assertEquals(CSV, pojo.getType()); + assertEquals(params, pojo.getParameters()); + + assertEquals(busyJobs.get(1).getJobId(), busy2.getJobId()); + pojo = ReportDataPojo.deserialize(busyJobs.get(1).getInput()); + assertEquals("report2 (busy)", pojo.getReportURI()); + assertEquals(PDF, pojo.getType()); + assertTrue(pojo.getParameters().isEmpty()); + + assertEquals(pendingJobs.get(0).getJobId(), pending1.getJobId()); + pojo = ReportDataPojo.deserialize(pendingJobs.get(0).getInput()); + assertEquals("report3 (pending)", pojo.getReportURI()); + assertEquals(XLSX, pojo.getType()); + + pojo = ReportDataPojo.deserialize(pendingJobs.get(1).getInput()); + assertEquals("report4 (pending)", pojo.getReportURI()); + assertEquals(CSV, pojo.getType()); + assertNull(pojo.getParameters()); + } + + @Test + public void test_markBusyJobsAsPending() { + ReportingJob job = new ReportingJob().setJobType(REPORT).setStatus(BUSY).setOwnerId(adminUser).setQueue(""); + job = jobService.saveJob(job); + + jobService.markBusyJobsAsPending(); + + job = jobService.loadJobById(job.getJobId()); + assertEquals(PENDING, job.getStatus()); + } + + @Test + public void test_markJobAsCompleted_withValidJobId() { + ReportingJob pendingJob = new ReportingJob().setJobType(REPORT).setStatus(PENDING).setOwnerId(adminUser).setQueue(""); + pendingJob = jobService.saveJob(pendingJob); + + ReportingJob completedJob = jobService.markJobAsCompleted(pendingJob.getJobId(), "somewhere"); + + assertEquals(COMPLETED, completedJob.getStatus()); + assertEquals("somewhere", new String(completedJob.getOutput())); + + // everything correct in the database? + List allJobs = allJobs(); + assertThat(allJobs, hasSize(1)); + assertEquals(COMPLETED, allJobs.get(0).getStatus()); + assertEquals("somewhere", new String(allJobs.get(0).getOutput())); + } + + @Test + public void test_markJobAsCompleted_withInvalidJobId() { + assertNull(jobService.markJobAsCompleted(42, "somewhere")); + + assertThat(allJobs(), is(empty())); + } + + @Test + public void test_markJobAsFailed_withValidJobId() { + ReportingJob pendingJob = new ReportingJob().setJobType(REPORT).setStatus(PENDING).setOwnerId(adminUser).setQueue(""); + pendingJob = jobService.saveJob(pendingJob); + + ReportingJob failedJob = jobService.markJobAsFailed(pendingJob.getJobId()); + + assertEquals(FAILED, failedJob.getStatus()); + assertNull(failedJob.getOutput()); + + // everything correct in the database? + List allJobs = allJobs(); + assertThat(allJobs, hasSize(1)); + assertEquals(FAILED, allJobs.get(0).getStatus()); + assertNull(allJobs.get(0).getOutput()); + } + + @Test + public void test_markJobAsFailed_withInvalidJobId() { + assertNull(jobService.markJobAsFailed(42)); + + assertThat(allJobs(), is(empty())); + } + + @Test + public void test_cleanUpOldJobsAndReportFiles() throws IOException { + Instant now = Instant.now(); + Date beforeMaxAge = Date.from(now.minus(MAX_AGE).minusSeconds(1000)); + Date afterMaxAge = Date.from(now.minus(MAX_AGE).plusSeconds(1000)); + + // create jobs and their associated output files + File fileToBeDeleted = File.createTempFile("ReportingJobServiceTest", "test"); + File fileToBeKept = File.createTempFile("ReportingJobServiceTest", "test"); + fileToBeDeleted.deleteOnExit(); + fileToBeKept.deleteOnExit(); + assertTrue(fileToBeDeleted.exists()); + assertTrue(fileToBeKept.exists()); + + ReportingJob jobToBeDeleted = new ReportingJob().setJobDate(beforeMaxAge).setJobType(REPORT).setStatus(COMPLETED) + .setOwnerId(adminUser).setQueue("").setOutput(fileToBeDeleted.getAbsolutePath().getBytes()); + ReportingJob jobToBeKept = new ReportingJob().setJobDate(afterMaxAge).setJobType(REPORT).setStatus(COMPLETED).setOwnerId(adminUser) + .setQueue("").setOutput(fileToBeKept.getAbsolutePath().getBytes()); + jobToBeDeleted = jobService.saveJob(jobToBeDeleted); + jobToBeKept = jobService.saveJob(jobToBeKept); + + // create orphaned files in the reports directory + File orphanedFileToBeDeleted = createTempFileInReportsDirectory(); + File orphanedFileToBeKept = createTempFileInReportsDirectory(); + assertTrue(orphanedFileToBeDeleted.exists()); + assertTrue(orphanedFileToBeKept.exists()); + orphanedFileToBeDeleted.setLastModified(beforeMaxAge.getTime()); + orphanedFileToBeKept.setLastModified(afterMaxAge.getTime()); + + // execution + jobService.cleanUpOldJobsAndReportFiles(); + + // check jobs and their associated output files + List allJobs = allJobs(); + assertThat(allJobs, hasSize(1)); + assertEquals(jobToBeKept.getJobId(), allJobs.get(0).getJobId()); + + assertFalse(fileToBeDeleted.exists()); + assertTrue(fileToBeKept.exists()); + + // check orphaned files in the reports directory + assertFalse(orphanedFileToBeDeleted.exists()); + assertTrue(orphanedFileToBeKept.exists()); + } + + private File createTempFileInReportsDirectory() throws IOException { + File reportsDir = new File(reportsDirectory.getReportsDirectory()); + File tempFile = File.createTempFile("ReportingJobServiceTest", "test", reportsDir); + tempFile.deleteOnExit(); + return tempFile; + } + + private List allJobs() { + Map cmap = new HashMap<>(); + cmap.put(CONDITION_JOBTYPE, REPORT); + return jobService.loadJobs(cmap); + } + + private List pendingJobs() { + Map cmap = new HashMap<>(); + cmap.put(CONDITION_JOBTYPE, REPORT); + cmap.put(CONDITION_STATUS, PENDING); + return jobService.loadJobs(cmap); + } + + private List busyJobs() { + Map cmap = new HashMap<>(); + cmap.put(CONDITION_JOBTYPE, REPORT); + cmap.put(CONDITION_STATUS, BUSY); + return jobService.loadJobs(cmap); + } + + private ReportingJob submit(ReportDataPojo pojo, Integer userId) { + ReportingJob job = new ReportingJob() + .setStatus(PENDING) + .setOwnerId(userId) + .setQueue("") + .setInput(pojo.serialize()); + return jobService.saveJob(job); + } +} diff --git a/reporting/src/test/java/de/ipb_halle/reporting/mocks/ReportsDirectoryMock.java b/reporting/src/test/java/de/ipb_halle/reporting/mocks/ReportsDirectoryMock.java new file mode 100644 index 000000000..7a95aad01 --- /dev/null +++ b/reporting/src/test/java/de/ipb_halle/reporting/mocks/ReportsDirectoryMock.java @@ -0,0 +1,77 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.reporting.mocks; + +import de.ipb_halle.reporting.ReportsDirectory; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.annotation.PreDestroy; +import javax.ejb.Lock; +import javax.ejb.LockType; +import javax.ejb.Singleton; +import javax.ejb.Startup; + +import org.apache.commons.io.FileUtils; + +/** + * + * @author fbroda, flange + */ +@Singleton(name="reportsDirectory") +//@Startup +public class ReportsDirectoryMock extends ReportsDirectory { + + private static final long serialVersionUID = 1L; + + /* + * The reports directory is initialized lazily, i.e. only when requested. Most + * tests won't need it. The shutdown() method needs to take care for its + * recursive deletion. Marking it with File.deleteOnExit() may not work, because + * it does not delete recursively. + */ + private File reportsDirectory = null; + + @PreDestroy + private void shutdown() throws IOException { + if (reportsDirectory != null) { + FileUtils.deleteDirectory(reportsDirectory); + reportsDirectory = null; + } + } + + private void initializeReportsDirectory() { + try { + reportsDirectory = Files.createTempDirectory("reports").toFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Lock(LockType.WRITE) + @Override + public String getReportsDirectory() { + if (reportsDirectory == null) { + initializeReportsDirectory(); + } + return reportsDirectory.getAbsolutePath(); + } +} diff --git a/reporting/src/test/resources/PostgresqlContainerSchemaFiles b/reporting/src/test/resources/PostgresqlContainerSchemaFiles new file mode 100644 index 000000000..8fc81a1ec --- /dev/null +++ b/reporting/src/test/resources/PostgresqlContainerSchemaFiles @@ -0,0 +1,5 @@ +# +# Configuration of PostgresqlContainerExtension: +# schema files for testing +# +00001.sql diff --git a/reporting/src/test/resources/arquillian.xml b/reporting/src/test/resources/arquillian.xml new file mode 100644 index 000000000..707677827 --- /dev/null +++ b/reporting/src/test/resources/arquillian.xml @@ -0,0 +1,49 @@ + + + + + + + 8800 + + + + target/arquillian + + apiDS = new://Resource?type=DataSource + apiDS.JdbcDriver = org.postgresql.Driver + apiDS.JdbcUrl = jdbc:postgresql://localhost:65432/lbac?charSet=UTF-8 + apiDS.UserName = lbac + apiDS.Password = lbac + apiDS.JtaManaged = true + apiDS.LogSql = true + # + # + tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar + tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ + postgresql*.jar,\ + log4j*.jar,slf4j*.jar,hibernate*.jar + + true + + + + + + + diff --git a/ui/src/test/resources/reports/readme.md b/reporting/src/test/resources/reports/readme.md similarity index 100% rename from ui/src/test/resources/reports/readme.md rename to reporting/src/test/resources/reports/readme.md diff --git a/ui/src/test/resources/reports/testReport1.prpt b/reporting/src/test/resources/reports/testReport1.prpt similarity index 100% rename from ui/src/test/resources/reports/testReport1.prpt rename to reporting/src/test/resources/reports/testReport1.prpt diff --git a/ui/src/test/resources/reports/testReport1/META-INF/manifest.xml b/reporting/src/test/resources/reports/testReport1/META-INF/manifest.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/META-INF/manifest.xml rename to reporting/src/test/resources/reports/testReport1/META-INF/manifest.xml diff --git a/ui/src/test/resources/reports/testReport1/content.xml b/reporting/src/test/resources/reports/testReport1/content.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/content.xml rename to reporting/src/test/resources/reports/testReport1/content.xml diff --git a/ui/src/test/resources/reports/testReport1/datadefinition.xml b/reporting/src/test/resources/reports/testReport1/datadefinition.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/datadefinition.xml rename to reporting/src/test/resources/reports/testReport1/datadefinition.xml diff --git a/ui/src/test/resources/reports/testReport1/dataschema.xml b/reporting/src/test/resources/reports/testReport1/dataschema.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/dataschema.xml rename to reporting/src/test/resources/reports/testReport1/dataschema.xml diff --git a/ui/src/test/resources/reports/testReport1/datasources/compound-ds.xml b/reporting/src/test/resources/reports/testReport1/datasources/compound-ds.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/datasources/compound-ds.xml rename to reporting/src/test/resources/reports/testReport1/datasources/compound-ds.xml diff --git a/ui/src/test/resources/reports/testReport1/datasources/sql-ds.xml b/reporting/src/test/resources/reports/testReport1/datasources/sql-ds.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/datasources/sql-ds.xml rename to reporting/src/test/resources/reports/testReport1/datasources/sql-ds.xml diff --git a/ui/src/test/resources/reports/testReport1/layout.xml b/reporting/src/test/resources/reports/testReport1/layout.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/layout.xml rename to reporting/src/test/resources/reports/testReport1/layout.xml diff --git a/ui/src/test/resources/reports/testReport1/meta.xml b/reporting/src/test/resources/reports/testReport1/meta.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/meta.xml rename to reporting/src/test/resources/reports/testReport1/meta.xml diff --git a/ui/src/test/resources/reports/testReport1/mimetype b/reporting/src/test/resources/reports/testReport1/mimetype similarity index 100% rename from ui/src/test/resources/reports/testReport1/mimetype rename to reporting/src/test/resources/reports/testReport1/mimetype diff --git a/ui/src/test/resources/reports/testReport1/settings.xml b/reporting/src/test/resources/reports/testReport1/settings.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/settings.xml rename to reporting/src/test/resources/reports/testReport1/settings.xml diff --git a/ui/src/test/resources/reports/testReport1/styles.xml b/reporting/src/test/resources/reports/testReport1/styles.xml similarity index 100% rename from ui/src/test/resources/reports/testReport1/styles.xml rename to reporting/src/test/resources/reports/testReport1/styles.xml diff --git a/ui/src/test/resources/reports/testReport1/translations.properties b/reporting/src/test/resources/reports/testReport1/translations.properties similarity index 100% rename from ui/src/test/resources/reports/testReport1/translations.properties rename to reporting/src/test/resources/reports/testReport1/translations.properties diff --git a/reporting/src/test/resources/schema/00001.sql b/reporting/src/test/resources/schema/00001.sql new file mode 100644 index 000000000..f21d3c97d --- /dev/null +++ b/reporting/src/test/resources/schema/00001.sql @@ -0,0 +1,122 @@ +/* + * Leibniz Bioactives Cloud + * Init script for database postgres 12.6 + * + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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. + * + *========================================================= + * + * Notes: none + * + *========================================================= + * + * define global vars + */ +\set LBAC_SCHEMA_VERSION '\'00001\'' + +\set LBAC_SCHEMA lbac +\set LBAC_DATABASE lbac +\set LBAC_USER lbac +\set LBAC_PW lbac +-- quoted stuff -- +\set LBAC_SCHEMA_QUOTED '\'' :LBAC_SCHEMA '\'' +\set LBAC_DATABASE_QUOTED '\'' :LBAC_DATABASE '\'' +\set LBAC_USER_QUOTED '\'' :LBAC_USER '\'' +\set LBAC_PW_QUOTED '\'' :LBAC_PW '\'' + +/* + *========================================================= + * + * terminate all session from database lbac -- + * getting db exclusive -- + */ +SELECT pg_terminate_backend(pg_stat_activity.pid) +FROM pg_stat_activity +WHERE pg_stat_activity.datname = :LBAC_DATABASE_QUOTED + AND pid <> pg_backend_pid(); + +/* + * clean up + */ +-- the following statement fails if LBAC_USER is not known! +-- REASSIGN OWNED BY :LBAC_USER TO postgres; +DROP SCHEMA IF EXISTS :LBAC_SCHEMA CASCADE; +DROP DATABASE IF EXISTS :LBAC_DATABASE; +DROP USER IF EXISTS :LBAC_USER; + +/* + * (re-)create database objects + */ +-- roles -- +CREATE USER :LBAC_USER PASSWORD :LBAC_PW_QUOTED; +-- db -- +CREATE DATABASE :LBAC_DATABASE WITH ENCODING 'UTF8' OWNER :LBAC_USER; + +\connect :LBAC_DATABASE + +-- skipping Bingo database extension (chemistry) -- + +-- schema -- +CREATE SCHEMA AUTHORIZATION :LBAC_USER; + +-- adjust schema search path -- +ALTER USER :LBAC_USER SET search_path to :LBAC_SCHEMA,public; + +-- privileges -- +GRANT USAGE ON SCHEMA :LBAC_SCHEMA, public TO :LBAC_USER; +GRANT CONNECT, TEMPORARY, TEMP ON DATABASE :LBAC_DATABASE to :LBAC_USER; +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA :LBAC_SCHEMA to :LBAC_USER; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to :LBAC_USER; +REVOKE ALL ON ALL TABLES IN SCHEMA :LBAC_SCHEMA FROM public; + +-- check for usefull extensions and install it -- +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +\connect - :LBAC_USER + +BEGIN TRANSACTION; + +-- tables -- + +CREATE TABLE info ( + key VARCHAR NOT NULL PRIMARY KEY, + value VARCHAR, + owner_id INTEGER, + aclist_id INTEGER +); +INSERT INTO info VALUES ('SETTING_JOB_SECRET', 'test_secret', null, null); + +/** + * - CRIMSy Jobs + */ +CREATE TABLE jobs ( + jobid SERIAL NOT NULL PRIMARY KEY, + input BYTEA, + jobdate TIMESTAMP NOT NULL DEFAULT now(), + jobtype INTEGER NOT NULL, + output BYTEA, + ownerid INTEGER, + queue VARCHAR NOT NULL, + status INTEGER NOT NULL +); + +CREATE TABLE reports ( + id SERIAL NOT NULL PRIMARY KEY, + context VARCHAR NOT NULL, + name VARCHAR NOT NULL, + source VARCHAR NOT NULL); + +COMMIT; + diff --git a/reporting/src/test/resources/test-persistence.xml b/reporting/src/test/resources/test-persistence.xml new file mode 100644 index 000000000..d32311d3c --- /dev/null +++ b/reporting/src/test/resources/test-persistence.xml @@ -0,0 +1,55 @@ + + + + + + + + LBAC api + apiDS + + org.hibernate.jpa.HibernatePersistenceProvider + + de.ipb_halle.reporting.report.ReportEntity + de.ipb_halle.job.JobEntity + + true + + + + + + + + + + + + + + + + + diff --git a/ui/pom.xml b/ui/pom.xml index 3b7c2e1c8..82a0c8cdd 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -45,7 +45,6 @@ plus 7.0.81 3.1.0 - 9.2.0.4-591 @@ -60,15 +59,6 @@ true false - - pentaho-releases - - - https://repo.orl.eng.hitachivantara.com/artifactory/pnt-mvn - - true - true - primefaces themes https://repository.primefaces.org/ @@ -273,10 +263,17 @@ - + + + de.ipb-halle + crimsy-job-api + 1.0 + + + de.ipb-halle - crimsy-agency-api + crimsy-job-storage 1.0 @@ -641,14 +638,19 @@ - + + + org.apache.httpcomponents + httpclient + 4.5.14 + + de.ipb-halle @@ -680,64 +682,5 @@ 1.0.0 - - - org.pentaho.reporting.engine - classic-core - ${pentaho.version} - - - org.apache.xmlbeans - xmlbeans - - - org.apache.poi - poi-ooxml-schemas - - - org.apache.poi - poi-ooxml - - - org.apache.poi - poi - - - - - - - org.pentaho.reporting.engine - classic-extensions - ${pentaho.version} - - - - org.pentaho.reporting.engine - wizard-core - ${pentaho.version} - - - - org.pentaho.reporting.library - libfonts - ${pentaho.version} - pom - - - - org.pentaho.reporting.library - libloader - ${pentaho.version} - pom - - - - org.pentaho.reporting - pentaho-reporting - ${pentaho.version} - pom - - diff --git a/ui/src/main/java/de/ipb_halle/lbac/admission/SystemSettings.java b/ui/src/main/java/de/ipb_halle/lbac/admission/SystemSettings.java index 243d08a2e..b401e42ac 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/admission/SystemSettings.java +++ b/ui/src/main/java/de/ipb_halle/lbac/admission/SystemSettings.java @@ -44,7 +44,6 @@ public class SystemSettings implements Serializable { private final static long serialVersionUID = 1L; public final static String SETTING_FORCE_LOGIN = "SETTING_FORCE_LOGIN"; - public final static String SETTING_AGENCY_SECRET = "SETTING_AGENCY_SECRET"; public static final String SETTING_LOGIN_CUSTOM_TEXT = "SETTING_LOGIN_CUSTOM_TEXT"; public static final String SETTING_INSTITUTION_WEB = "SETTING_INSTITUTION_WEB"; public static final String SETTING_GDPR_CONTACT = "SETTING_GDPR_CONTACT"; diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJob.java b/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJob.java new file mode 100644 index 000000000..5a911e8fb --- /dev/null +++ b/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJob.java @@ -0,0 +1,76 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.device.job; + +import de.ipb_halle.crimsy_api.DTO; +import de.ipb_halle.job.Job; +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.job.JobType; +import de.ipb_halle.job.NetJob; +import de.ipb_halle.lbac.admission.User; + +import java.util.Date; + +/** + * PrintJob + * + * @author fbroda + */ +public class PrintJob extends Job { + private User owner; + + public PrintJob() { + super(); + setJobType(JobType.PRINT); + } + + public PrintJob(JobEntity entity, User owner) { + super(entity); + if (entity.getJobType() != JobType.PRINT) { + throw new IllegalArgumentException("Attempt to create PRINT job from non-print-job entity"); + } + this.owner = owner; + } + + @Override + public JobEntity createEntity() { + JobEntity e = super.createEntity(); + if (this.owner != null) { + e.setOwnerId(this.owner.getId()); + } + return e; + } + + + public User getOwner() { + return this.owner; + } + + public PrintJob setOwner(User owner) { + this.owner = owner; + return this; + } + + @Override + public void update(NetJob netJob) { + if (netJob.getJobType() != JobType.PRINT) { + throw new IllegalArgumentException("Attempt to update PRINT job from non-print-job NetJob"); + } + super.update(netJob); + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJobService.java b/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJobService.java new file mode 100644 index 000000000..3f56974b5 --- /dev/null +++ b/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintJobService.java @@ -0,0 +1,58 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.device.job; + +import de.ipb_halle.job.Job; +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.job.JobService; +import de.ipb_halle.lbac.admission.MemberService; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.ejb.Stateless; +import javax.inject.Inject; + +import org.apache.logging.log4j.Logger; + +/** + * PrintJobService loads, stores and deletes print jobs. + */ +@Stateless +public class PrintJobService extends JobService implements Serializable { + + @Inject + private MemberService memberService; + + @Override + protected PrintJob buildJob(JobEntity entity) { + if (entity != null) { + return new PrintJob(entity, memberService.loadUserById(entity.getOwnerId())); + } + return null; + } + + public PrintJob saveJob(PrintJob job) { + return new PrintJob(super.saveEntity(job), job.getOwner()); + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/job/JobWebService.java b/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintWebService.java similarity index 81% rename from ui/src/main/java/de/ipb_halle/lbac/device/job/JobWebService.java rename to ui/src/main/java/de/ipb_halle/lbac/device/job/PrintWebService.java index 22cb937a5..8a7398eb3 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/job/JobWebService.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/job/PrintWebService.java @@ -17,9 +17,10 @@ */ package de.ipb_halle.lbac.device.job; -import de.ipb_halle.lbac.admission.SystemSettings; -import de.ipb_halle.lbac.entity.InfoObject; -import de.ipb_halle.lbac.service.InfoObjectService; +import de.ipb_halle.job.NetJob; +import de.ipb_halle.job.JobService; +import de.ipb_halle.job.JobType; +import de.ipb_halle.job.TokenGenerator; import java.util.ArrayList; import java.util.HashMap; @@ -36,18 +37,15 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -@Path("/jobs") +@Path("/print") @Stateless -public class JobWebService { +public class PrintWebService { @Inject - private JobService jobService; - - @Inject - private InfoObjectService infoObjectService; + private PrintJobService jobService; private Logger logger; @@ -55,8 +53,8 @@ public class JobWebService { /** * default constructor */ - public JobWebService() { - this.logger = LogManager.getLogger(this.getClass().getName()); + public PrintWebService() { + this.logger = LoggerFactory.getLogger(this.getClass()); } /** @@ -80,12 +78,8 @@ public Response getJobs(NetJob request) { /** * obtain the secret from system settings */ - private String obtainSecret() { - InfoObject obj = infoObjectService.loadByKey(SystemSettings.SETTING_AGENCY_SECRET); - if (obj != null) { - return obj.getValue(); - } - return null; + private String obtainJobSecret() { + return jobService.obtainJobSecret(); } /** @@ -102,9 +96,10 @@ private NetJob processQuery(NetJob request) { List joblist = new ArrayList (); Map cmap = new HashMap (); - if (request.getJobType() != null) { - cmap.put(JobService.CONDITION_JOBTYPE, request.getJobType()); + if (request.getJobType() != JobType.PRINT) { + throw new IllegalArgumentException("Query for invalid job type"); } + cmap.put(JobService.CONDITION_JOBTYPE, JobType.PRINT); if (request.getQueue() != null) { cmap.put(JobService.CONDITION_QUEUE, request.getQueue()); @@ -114,7 +109,7 @@ private NetJob processQuery(NetJob request) { cmap.put(JobService.CONDITION_STATUS, request.getStatus()); } - for(Job job : jobService.loadJobs(cmap)) { + for(PrintJob job : jobService.loadJobs(cmap)) { joblist.add(job.createNetJob()); } return netjob.setJobList(joblist); @@ -133,7 +128,7 @@ private NetJob processRequest(NetJob request) { return null; } - String secret = obtainSecret(); + String secret = obtainJobSecret(); if ((secret == null) || (secret.length() < 8)) { this.logger.info("No secret configured or secret to short"); return null; @@ -164,7 +159,7 @@ private NetJob processRequest(NetJob request) { * process an update request */ private NetJob processUpdate(NetJob request) { - Job job = jobService.loadJobById(request.getJobId()); + PrintJob job = jobService.loadJobById(request.getJobId()); if (job != null) { job.setStatus(request.getStatus()); job.setOutput(request.getOutput()); diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/AbstractPrintDriver.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/AbstractPrintDriver.java index 2a8441ba9..d83ceb2ae 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/AbstractPrintDriver.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/AbstractPrintDriver.java @@ -22,8 +22,7 @@ import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobType; +import de.ipb_halle.lbac.device.job.PrintJob; import de.ipb_halle.lbac.util.HexUtil; import java.awt.Color; @@ -188,12 +187,12 @@ public PrintDriver clear() { * calls the transform() method and creates a print job. * @return the Job */ - public Job createJob() { + public PrintJob createJob() { transform(); - return new Job() - .setJobType(JobType.PRINT) - .setQueue(this.printer.getQueue()) + PrintJob job = new PrintJob(); + job.setQueue(this.printer.getQueue()) .setInput(Arrays.copyOf(this.buffer.array(), this.buffer.position())); + return job; } /** diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintAdminBean.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintAdminBean.java index dea43b1ef..7c5291e27 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintAdminBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintAdminBean.java @@ -27,8 +27,8 @@ import de.ipb_halle.lbac.admission.UserBean; import de.ipb_halle.lbac.globals.ACObjectController; import de.ipb_halle.lbac.globals.NavigationConstants; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; +import de.ipb_halle.lbac.device.job.PrintJob; +import de.ipb_halle.lbac.device.job.PrintJobService; import de.ipb_halle.lbac.navigation.Navigator; import java.io.Serializable; @@ -66,7 +66,7 @@ public class PrintAdminBean implements ACObjectBean, Serializable { private GlobalAdmissionContext globalAdmissionContext; @Inject - private JobService jobService; + private PrintJobService printJobService; @Inject private LabelService labelService; @@ -273,11 +273,11 @@ public void setDriverType(String driverType) { } /** - * submit a job for printing + * submit the test label for printing */ - public void submitJob(PrintDriver driver) { - Job job = driver.createJob(); - job.setOwner(userBean.getCurrentAccount()); - this.jobService.saveJob(job); + private void submitJob(PrintDriver driver) { + PrintJob printJob = driver.createJob(); + printJob.setOwner(userBean.getCurrentAccount()); + this.printJobService.saveJob(printJob); } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintBean.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintBean.java index 2c10f6ce6..2ce9e05e3 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintBean.java @@ -23,8 +23,8 @@ import com.google.gson.JsonParser; import de.ipb_halle.lbac.admission.UserBean; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; +import de.ipb_halle.lbac.device.job.PrintJob; +import de.ipb_halle.lbac.device.job.PrintJobService; import de.ipb_halle.lbac.util.HexUtil; import de.ipb_halle.lbac.util.pref.Preference; import de.ipb_halle.lbac.util.pref.PreferenceService; @@ -131,7 +131,7 @@ public class PrintBean implements Serializable { private static final long serialVersionUID = 1L; @Inject - private JobService jobService; + private PrintJobService printJobService; @Inject private PrinterService printerService; @@ -576,8 +576,8 @@ protected void setUserBean(UserBean userBean) { * submit a job for printing */ private void submitJob(PrintDriver driver) { - Job job = driver.createJob(); - job.setOwner(userBean.getCurrentAccount()); - this.jobService.saveJob(job); + PrintJob printJob = driver.createJob(); + printJob.setOwner(userBean.getCurrentAccount()); + this.printJobService.saveJob(printJob); } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintDriver.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintDriver.java index afb21442f..da95fb068 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintDriver.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/PrintDriver.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.device.print; -import de.ipb_halle.lbac.device.job.Job; +import de.ipb_halle.lbac.device.job.PrintJob; import java.awt.image.Raster; import java.util.ArrayList; @@ -31,7 +31,7 @@ public interface PrintDriver { public PrintDriver clear(); - public Job createJob(); + public PrintJob createJob(); public String getDefaultFontName(); public int getDefaultFontSize(); diff --git a/ui/src/main/java/de/ipb_halle/lbac/device/print/ZebraE2Driver.java b/ui/src/main/java/de/ipb_halle/lbac/device/print/ZebraE2Driver.java index 1c7f8d317..d87b6138b 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/device/print/ZebraE2Driver.java +++ b/ui/src/main/java/de/ipb_halle/lbac/device/print/ZebraE2Driver.java @@ -17,8 +17,6 @@ */ package de.ipb_halle.lbac.device.print; -import de.ipb_halle.lbac.device.job.Job; - import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.Logger; diff --git a/ui/src/main/java/de/ipb_halle/lbac/globals/InitApplication.java b/ui/src/main/java/de/ipb_halle/lbac/globals/InitApplication.java index 44c5a9ae5..8767b7e90 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/globals/InitApplication.java +++ b/ui/src/main/java/de/ipb_halle/lbac/globals/InitApplication.java @@ -119,6 +119,8 @@ private void healthCheck() { healthState, nodeService.getLocalNode(), collectionService, + infoObjectService, + this.globalAdmissionContext.getAdminOnlyACL(), this.globalAdmissionContext.getPublicReadACL(), this.globalAdmissionContext.getAdminAccount(), fs, @@ -131,6 +133,10 @@ private void healthCheck() { if (healthRepairer.isTaxonomyRepairNeeded()) { healthRepairer.repairRootTaxonomy(); } + + if (healthRepairer.isRepairOfJobSecretNeeded()) { + healthRepairer.repairJobSecret(); + } } private void restCheck() { diff --git a/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthState.java b/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthState.java index fab367b1a..8b6479c9d 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthState.java +++ b/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthState.java @@ -33,6 +33,7 @@ public class HealthState { public State publicCollectionFileState = State.UNCHECKED; public State publicCollectionFileSyncState = State.UNCHECKED; public State rootTaxonomy = State.UNCHECKED; + public State jobSecretState = State.UNCHECKED; public Map collectionFileSyncList = new HashMap<>(); diff --git a/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateCheck.java b/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateCheck.java index 96cd71ea1..80b449977 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateCheck.java +++ b/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateCheck.java @@ -17,6 +17,7 @@ */ package de.ipb_halle.lbac.globals.health; +import static de.ipb_halle.job.JobService.SETTING_JOB_SECRET; import de.ipb_halle.lbac.collections.Collection; import de.ipb_halle.lbac.entity.InfoObject; import de.ipb_halle.lbac.entity.Node; @@ -82,6 +83,7 @@ public HealthStateCheck( public HealthState checkHealthState() { checkDbState(); checkFileSystemState(); + checkJobSecret(); checkPublicCollectionSync(); checkSyncOfLocalCollections(); checkRootTaxonomy(); @@ -132,6 +134,16 @@ private void checkFileSystemState() { } + private void checkJobSecret() { + try { + if (infoObjectService.loadByKey(SETTING_JOB_SECRET).getValue() != null) {; + healthState.jobSecretState = HealthState.State.OK; + } + } catch (Exception e) { + healthState.jobSecretState = HealthState.State.FAILED; + } + } + private void checkRootTaxonomy() { if (taxonomyService.checkRootTaxonomy() == 0) { healthState.rootTaxonomy = HealthState.State.FAILED; @@ -204,6 +216,7 @@ private HealthState reportHealthState() { logger.info(String.format("* local public collection in db: %s", healthState.publicCollectionDbState)); logger.info(String.format("* local public collection in sync with file: %s", healthState.publicCollectionFileState)); + logger.info(String.format("* job secret configured: %s", healthState.jobSecretState)); boolean publicColInSync = healthState.publicCollectionFileSyncState == State.OK; logger.info(String.format("* local public collection sync check: %sOK", publicColInSync ? "" : "NOT ")); diff --git a/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateRepair.java b/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateRepair.java index 524c4dc2d..dd58d8fa1 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateRepair.java +++ b/ui/src/main/java/de/ipb_halle/lbac/globals/health/HealthStateRepair.java @@ -18,10 +18,12 @@ package de.ipb_halle.lbac.globals.health; import de.ipb_halle.lbac.admission.ACList; +import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.collections.Collection; +import de.ipb_halle.lbac.entity.InfoObject; import de.ipb_halle.lbac.entity.Node; -import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.service.FileService; +import de.ipb_halle.lbac.service.InfoObjectService; import de.ipb_halle.lbac.globals.health.HealthState.State; import de.ipb_halle.lbac.material.biomaterial.Taxonomy; import de.ipb_halle.lbac.material.biomaterial.TaxonomyLevel; @@ -34,9 +36,12 @@ import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.UUID; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import static de.ipb_halle.job.JobService.SETTING_JOB_SECRET; + /** * Tries to repair some discrepancies found by the healtcheck procedere * @@ -50,6 +55,8 @@ public class HealthStateRepair { private HealthState healthState; private Node localNode; private CollectionService collectionService; + private InfoObjectService infoObjectService; + private ACList adminOnlyAcl; private ACList publicReadAcl; private User adminAccount; private FileService fileService; @@ -59,6 +66,8 @@ public HealthStateRepair(String publicCollectionName, HealthState healthState, Node localNode, CollectionService collectionService, + InfoObjectService infoObjectService, + ACList adminOnlyAcl, ACList publicReadAcl, User adminAccount, FileService fileService, @@ -68,6 +77,8 @@ public HealthStateRepair(String publicCollectionName, this.healthState = healthState; this.localNode = localNode; this.collectionService = collectionService; + this.infoObjectService = infoObjectService; + this.adminOnlyAcl = adminOnlyAcl; this.publicReadAcl = publicReadAcl; this.adminAccount = adminAccount; this.fileService = fileService; @@ -128,6 +139,10 @@ public boolean isRepairOfPublicCollectionNeeded() { return false; } + public boolean isRepairOfJobSecretNeeded() { + return healthState.jobSecretState != State.OK; + } + public boolean isTaxonomyRepairNeeded() { return healthState.rootTaxonomy == State.FAILED; } @@ -171,6 +186,14 @@ private Collection createPublicCollectionInDb() { } + public void repairJobSecret() { + String secret = UUID.randomUUID().toString(); + InfoObject secretInfo = new InfoObject(SETTING_JOB_SECRET, secret); + secretInfo.setOwner(adminAccount); + secretInfo.setACList(adminOnlyAcl); + infoObjectService.save(secretInfo); + } + private boolean updatePublicCollectionInDb() { try { publicCollection.setName(publicCollectionName); diff --git a/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java b/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java index 62b244c88..0c91d68d8 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/items/bean/ItemOverviewBean.java @@ -33,7 +33,7 @@ import de.ipb_halle.lbac.material.common.service.MaterialService; import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.lbac.reporting.report.ReportMgr; +import de.ipb_halle.lbac.reporting.ReportMgr; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.items.ItemHistory; import de.ipb_halle.lbac.items.bean.aliquot.createsolution.CreateSolutionBean; @@ -42,8 +42,8 @@ import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.lbac.util.NonEmpty; -import de.ipb_halle.reporting.report.Report; -import de.ipb_halle.reporting.report.ReportType; +import de.ipb_halle.reporting.Report; +import de.ipb_halle.reporting.ReportType; import java.io.Serializable; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java b/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java index 1e811abf2..fa3e6a235 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/material/common/bean/MaterialOverviewBean.java @@ -31,7 +31,7 @@ import de.ipb_halle.lbac.material.MaterialType; import de.ipb_halle.lbac.navigation.Navigator; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.lbac.reporting.report.ReportMgr; +import de.ipb_halle.lbac.reporting.ReportMgr; import de.ipb_halle.lbac.util.resources.ResourceLocation; import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.material.JsfMessagePresenter; @@ -40,8 +40,8 @@ import de.ipb_halle.lbac.material.composition.Concentration; import de.ipb_halle.lbac.material.structure.Molecule; import de.ipb_halle.lbac.util.NonEmpty; -import de.ipb_halle.reporting.report.Report; -import de.ipb_halle.reporting.report.ReportType; +import de.ipb_halle.reporting.Report; +import de.ipb_halle.reporting.ReportType; import java.io.Serializable; import java.util.ArrayList; diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJob.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJob.java new file mode 100644 index 000000000..f46764687 --- /dev/null +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJob.java @@ -0,0 +1,123 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2023 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.reporting; + +import de.ipb_halle.crimsy_api.DTO; +import de.ipb_halle.job.Job; +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.job.JobType; +import de.ipb_halle.job.JobStatus; +import de.ipb_halle.lbac.admission.User; + +import java.io.File; +import java.util.Date; + +/** + * ReportJob + * Not to be confused with ReportingJob from the reporting module, + * which does not care of job ownership. + * + * @author fbroda + */ +public class ReportJob extends Job { + private User owner; + + public ReportJob() { + super(); + setJobType(JobType.REPORT); + } + + public ReportJob(JobEntity entity, User owner) { + super(entity); + if (entity.getJobType() != JobType.REPORT) { + throw new IllegalArgumentException("Attempt to create REPORT job from non-report-job entity"); + } + this.owner = owner; + } + + @Override + public JobEntity createEntity() { + JobEntity e = super.createEntity(); + if (this.owner != null) { + e.setOwnerId(this.owner.getId()); + } + return e; + } + + @Override + public Integer getOwnerId() { + if (this.owner != null) { + return this.owner.getId(); + } + return null; + } + + public User getOwner() { + return this.owner; + } + + public ReportJob setOwner(User owner) { + this.owner = owner; + return this; + } + + @Override + public ReportJob setOwnerId(Integer o) { + throw new IllegalArgumentException("setting of ownerId not allowed"); + } + + public String getI18nKeyForStatus() { + JobStatus status = getStatus(); + if (status == null) { + return ""; + } + return "jobStatus_" + status.toString(); + } + + public boolean isDownloadable() { + return isCompleted() && outputFileExists(); + } + + public boolean isDeleteable() { + return isFailed() || isCompleted(); + } + + public String getRowStyleClass() { + JobStatus status = getStatus(); + if (status == null) { + return ""; + } + return "report-" + status.toString().toLowerCase(); + } + + private boolean isFailed() { + return JobStatus.FAILED.equals(getStatus()); + } + + private boolean isCompleted() { + return JobStatus.COMPLETED.equals(getStatus()); + } + + private boolean outputFileExists() { + byte[] output = getOutput(); + if (output == null) { + return false; + } + return new File(new String(output)).exists(); + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJobService.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJobService.java new file mode 100644 index 000000000..e7f5d0c2f --- /dev/null +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportJobService.java @@ -0,0 +1,154 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.reporting; + +import static de.ipb_halle.job.JobService.CONDITION_JOBTYPE; +import static de.ipb_halle.job.JobService.CONDITION_OWNERID; +import static de.ipb_halle.job.JobService.CONDITION_STATUS; +import static de.ipb_halle.job.JobStatus.BUSY; +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobStatus.FAILED; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobType.REPORT; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.RejectedExecutionException; +import javax.ejb.Stateless; +import javax.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import de.ipb_halle.lbac.admission.GlobalAdmissionContext; +import de.ipb_halle.lbac.admission.MemberService; +import de.ipb_halle.lbac.admission.User; +import de.ipb_halle.job.JobEntity; +import de.ipb_halle.job.JobService; +import de.ipb_halle.reporting.ReportDataPojo; + +/** + * + * @author flange + */ +@Stateless +public class ReportJobService extends JobService { + private Logger logger = LogManager.getLogger(getClass().getName()); + + /** + * Maximum age of reporting jobs in the database and files in the reports + * directory. + */ + static final Duration MAX_AGE = Duration.ofDays(7); + + @Inject + private MemberService memberService; + + @Override + protected ReportJob buildJob(JobEntity entity) { + if (entity != null) { + return new ReportJob(entity, memberService.loadUserById(entity.getOwnerId())); + } + return null; + } + + /** + * Delete the given reporting job and its report file. + * + * @param job + */ + public void deleteJob(ReportJob job) { + removeJob(job); + byte[] output = job.getOutput(); + if (output != null) { + deleteFileIfExists(new String(output)); + } + } + + + private void deleteFileIfExists(String filename) { + File file = new File(filename); + if (file.exists()) { + file.delete(); + } + } + + /** + * Request the report file of the given reporting job. + * + * @param job + * @return report file or null in case the report job does not exist in the + * database, does not have a report file or the file does not exist + */ + public File getOutputFileOfJob(ReportJob job) { + ReportJob jobFromDB = loadJobById(job.getJobId()); + if (jobFromDB == null) { + return null; + } + + byte[] output = jobFromDB.getOutput(); + if (output == null) { + return null; + } + + String outputFile = new String(output); + File f = new File(outputFile); + if (!f.exists()) { + return null; + } + + return f; + } + + public List loadJobsForUser(User u) { + Map cmap = new HashMap<>(); + cmap.put(CONDITION_JOBTYPE, REPORT); + cmap.put(CONDITION_OWNERID, u.getId()); + + ArrayList jobs = new ArrayList<> (); + for (ReportJob j : loadJobs(cmap)) { + jobs.add(new ReportJob(j.createEntity(), memberService.loadUserById(j.getOwnerId()))); + } + return jobs; + } + + /** + * Create a new pending reporting job + * + * @param reportDataPojo + * @param owner + * @return the ReportJob object + */ + public ReportJob submit(ReportDataPojo reportDataPojo, User owner) { + ReportJob newJob = new ReportJob().setOwner(owner).setStatus(PENDING).setQueue("") + .setInput(reportDataPojo.serialize()); + return saveJob(newJob); + } +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportMgr.java similarity index 91% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java rename to ui/src/main/java/de/ipb_halle/lbac/reporting/ReportMgr.java index dbf70aaba..49ef13f10 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/report/ReportMgr.java +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportMgr.java @@ -15,14 +15,13 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.lbac.reporting; import de.ipb_halle.lbac.admission.User; -import de.ipb_halle.lbac.reporting.job.ReportJobPojo; -import de.ipb_halle.lbac.reporting.job.ReportJobService; -import de.ipb_halle.reporting.report.Report; -import de.ipb_halle.reporting.report.ReportService; -import de.ipb_halle.reporting.report.ReportType; +import de.ipb_halle.reporting.Report; +import de.ipb_halle.reporting.ReportDataPojo; +import de.ipb_halle.reporting.ReportService; +import de.ipb_halle.reporting.ReportType; import java.util.Comparator; import java.util.List; @@ -130,7 +129,7 @@ private String extractNameFromLanguageElement(JsonElement languageElement) { * @param currentUser current user */ public void submitReport(Report report, Map parameters, ReportType type, User currentUser) { - ReportJobPojo pojo = new ReportJobPojo(report.getSource(), type, parameters); + ReportDataPojo pojo = new ReportDataPojo(report.getSource(), type, parameters); reportJobService.submit(pojo, currentUser); } } diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobsBean.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportingJobsBean.java similarity index 60% rename from ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobsBean.java rename to ui/src/main/java/de/ipb_halle/lbac/reporting/ReportingJobsBean.java index f4496976f..24134391a 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobsBean.java +++ b/ui/src/main/java/de/ipb_halle/lbac/reporting/ReportingJobsBean.java @@ -15,7 +15,7 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.jobview; +package de.ipb_halle.lbac.reporting; import java.io.File; import java.io.IOException; @@ -28,10 +28,10 @@ import javax.inject.Named; import de.ipb_halle.lbac.admission.UserBean; -import de.ipb_halle.lbac.device.job.Job; import de.ipb_halle.lbac.material.MessagePresenter; -import de.ipb_halle.lbac.reporting.job.ReportJobService; import de.ipb_halle.lbac.util.jsf.SendFileBean; +import de.ipb_halle.lbac.reporting.ReportJob; +import de.ipb_halle.lbac.reporting.ReportJobService; /** * Controller for myReports.xhtml @@ -55,54 +55,54 @@ public class ReportingJobsBean implements Serializable { @Inject private transient MessagePresenter messagePresenter; - private List reportingJobs; + private List reportJobs; @PostConstruct void init() { - loadReportingJobs(); + loadReportJobs(); } - private void loadReportingJobs() { - List newReportingJobs = new ArrayList<>(); - for (Job job : reportJobService.loadJobsForUser(userBean.getCurrentAccount())) { - newReportingJobs.add(new ReportingJobWapper(job)); + private void loadReportJobs() { + List newReportJobs = new ArrayList<>(); + for (ReportJob job : reportJobService.loadJobsForUser(userBean.getCurrentAccount())) { + newReportJobs.add(job); } - reportingJobs = newReportingJobs; + reportJobs = newReportJobs; } /* * Actions */ public void actionReloadTable() { - loadReportingJobs(); + loadReportJobs(); } - public void actionDownloadReport(ReportingJobWapper wrapper) throws IOException { - if (!wrapper.isDownloadable()) { + public void actionDownloadReport(ReportJob job) throws IOException { + if (!job.isDownloadable()) { return; } - File f = reportJobService.getOutputFileOfJob(wrapper.getJob()); + File f = reportJobService.getOutputFileOfJob(job); if (f != null) { sendFileBean.sendFile(f); } } - public void actionDeleteReport(ReportingJobWapper wrapper) { - if (wrapper.isDeleteable()) { - reportJobService.deleteJob(wrapper.getJob()); - loadReportingJobs(); + public void actionDeleteReport(ReportJob job) { + if (job.isDeleteable()) { + reportJobService.deleteJob(job); + loadReportJobs(); } } /* * Getters */ - public List getReportingJobs() { - return reportingJobs; + public List getReportingJobs() { + return reportJobs; } - public String getJobStatusI18n(ReportingJobWapper wrapper) { - return messagePresenter.presentMessage(wrapper.getI18nKeyForStatus()); + public String getJobStatusI18n(ReportJob job) { + return messagePresenter.presentMessage(job.getI18nKeyForStatus()); } -} \ No newline at end of file +} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java deleted file mode 100644 index 9f515981d..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/job/ReportJobPojo.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.reporting.job; - -import java.io.Serializable; -import java.util.Map; - -import de.ipb_halle.reporting.report.ReportType; - -/** - * Serializable data class to be used as input data of reporting jobs. - * - * @author flange - */ -public class ReportJobPojo implements Serializable { - private static final long serialVersionUID = 1L; - - private String reportURI; - private ReportType type; - private Map parameters; - - public ReportJobPojo(String reportURI, ReportType type, Map parameters) { - this.reportURI = reportURI; - this.type = type; - this.parameters = parameters; - } - - public String getReportURI() { - return reportURI; - } - - public ReportType getType() { - return type; - } - - public Map getParameters() { - return parameters; - } -} diff --git a/ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapper.java b/ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapper.java deleted file mode 100644 index 6ea50f6ca..000000000 --- a/ui/src/main/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapper.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.reporting.jobview; - -import java.io.File; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobStatus; - -/** - * Wrapper class for reporting job objects. - * - * @author flange - */ -public class ReportingJobWapper { - private final Job job; - - public ReportingJobWapper(Job job) { - this.job = job; - } - - public Job getJob() { - return job; - } - - public String getI18nKeyForStatus() { - JobStatus status = job.getStatus(); - if (status == null) { - return ""; - } - return "jobStatus_" + status.toString(); - } - - public boolean isDownloadable() { - return isCompleted() && outputFileExists(); - } - - public boolean isDeleteable() { - return isFailed() || isCompleted(); - } - - public String getRowStyleClass() { - JobStatus status = job.getStatus(); - if (status == null) { - return ""; - } - return "report-" + status.toString().toLowerCase(); - } - - private boolean isFailed() { - return JobStatus.FAILED.equals(job.getStatus()); - } - - private boolean isCompleted() { - return JobStatus.COMPLETED.equals(job.getStatus()); - } - - private boolean outputFileExists() { - byte[] output = job.getOutput(); - if (output == null) { - return false; - } - return new File(new String(output)).exists(); - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/ipb_halle/lbac/webservice/ApplicationConfig.java b/ui/src/main/java/de/ipb_halle/lbac/webservice/ApplicationConfig.java index 423436742..915a610d8 100644 --- a/ui/src/main/java/de/ipb_halle/lbac/webservice/ApplicationConfig.java +++ b/ui/src/main/java/de/ipb_halle/lbac/webservice/ApplicationConfig.java @@ -20,7 +20,7 @@ import de.ipb_halle.lbac.admission.MembershipWebService; import de.ipb_halle.lbac.admission.group.DeactivateGroupWebService; import de.ipb_halle.lbac.collections.CollectionWebService; -import de.ipb_halle.lbac.device.job.JobWebService; +import de.ipb_halle.lbac.device.job.PrintWebService; import de.ipb_halle.lbac.forum.postings.PostingWebService; import de.ipb_halle.lbac.forum.topics.TopicsWebService; import de.ipb_halle.lbac.search.SearchWebService; @@ -46,7 +46,7 @@ public Set> getClasses() { WordCloudWebService.class, TopicsWebService.class, PostingWebService.class, - JobWebService.class, + PrintWebService.class, SimpleRESTPojoExample.class, DocumentWebService.class)); } diff --git a/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_de.properties b/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_de.properties index ca99c7ff9..88fbe8d98 100644 --- a/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_de.properties +++ b/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_de.properties @@ -833,8 +833,8 @@ searchMgr_resulttable_type=Typ searchMgr_searchtext_placeholder=Bitte Suchbegriffe hier eingeben ... searchMgr_uploadbutton=Dokument zum Upload SETTINGS_SAVED=Systemeinstellungen gespeichert. -SETTING_AGENCY_SECRET=Agency Passwort -SETTING_AGENCY_SECRET_detail=Passwort für den Job Scheduler. +SETTING_JOB_SECRET=Agency Passwort +SETTING_JOB_SECRET_detail=Passwort für den Job Scheduler. SETTING_LOGIN_CUSTOM_TEXT=Infotext beim Login SETTING_LOGIN_CUSTOM_TEXT_detail=Infotext beim Login SETTING_FORCE_LOGIN=Login erzwingen diff --git a/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_en.properties b/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_en.properties index 22b362944..359a1af14 100644 --- a/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_en.properties +++ b/ui/src/main/resources/de/ipb_halle/lbac/i18n/messages_en.properties @@ -833,8 +833,8 @@ searchMgr_resulttable_type=Type searchMgr_searchtext_placeholder=Enter your search query here ... searchMgr_uploadbutton=Document to upload SETTINGS_SAVED=System settings saved. -SETTING_AGENCY_SECRET=Agency password -SETTING_AGENCY_SECRET_detail=Shared secret for the job scheduler. +SETTING_JOB_SECRET=Agency password +SETTING_JOB_SECRET_detail=Shared secret for the job scheduler. SETTING_LOGIN_CUSTOM_TEXT=Infotext at login SETTING_LOGIN_CUSTOM_TEXT_detail=Infotext at login SETTING_FORCE_LOGIN=Force login diff --git a/ui/src/test/java/de/ipb_halle/lbac/EntityManagerService.java b/ui/src/test/java/de/ipb_halle/lbac/EntityManagerService.java deleted file mode 100644 index cd80d3f89..000000000 --- a/ui/src/test/java/de/ipb_halle/lbac/EntityManagerService.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2020 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac; - -import java.util.List; -import javax.ejb.Stateless; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; - -/** - * - * @author fmauz - */ -@Stateless -public class EntityManagerService { - - @PersistenceContext(name = "de.ipb_halle.lbac") - private EntityManager em; - - public void flush() { - this.em.flush(); - } - - public void doSqlUpdate(String query) { - em.createNativeQuery(query).executeUpdate(); - } - - @SuppressWarnings("unchecked") - public List doSqlQuery(String query) { - return em.createNativeQuery(query).getResultList(); - } - - public void deleteUserWithAllMemberships(String userId) { - this.em.createNativeQuery("delete from memberships where member_id='" + userId + "'").executeUpdate(); - } - - @SuppressWarnings("unchecked") - public void removeEntity(Class clazz, Object id) { - this.em.remove(this.em.find(clazz, id)); - - } - - public EntityManager getEntityManager() { - return em; - } -} diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/UserPluginSettingsBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/UserPluginSettingsBeanTest.java index 5f98409ea..7b8ec328c 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/UserPluginSettingsBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/UserPluginSettingsBeanTest.java @@ -38,11 +38,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.util.WebXml; import de.ipb_halle.lbac.util.pref.PreferenceService; import de.ipb_halle.molecularfaces.component.molplugin.MolPluginCore.PluginType; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @ExtendWith(PostgresqlContainerExtension.class) @@ -259,4 +259,4 @@ public void finish() { this.entityManagerService.removeEntity(UserEntity.class, this.user.getId()); } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/admission/UserTimeZoneSettingsBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/admission/UserTimeZoneSettingsBeanTest.java index 730cfe7fa..bbbf94882 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/admission/UserTimeZoneSettingsBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/admission/UserTimeZoneSettingsBeanTest.java @@ -32,10 +32,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.timezone.TimeZonesBean; import de.ipb_halle.lbac.util.pref.PreferenceService; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @ExtendWith(PostgresqlContainerExtension.class) @@ -108,4 +108,4 @@ public void finish() { this.entityManagerService.removeEntity(UserEntity.class, this.user.getId()); } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/ContainerCreator.java b/ui/src/test/java/de/ipb_halle/lbac/base/ContainerCreator.java index c67d35a18..ed00d99e4 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/ContainerCreator.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/ContainerCreator.java @@ -17,10 +17,10 @@ */ package de.ipb_halle.lbac.base; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.container.Container; import de.ipb_halle.lbac.container.ContainerType; import de.ipb_halle.lbac.container.service.ContainerService; +import de.ipb_halle.test.EntityManagerService; /** * diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/ItemCreator.java b/ui/src/test/java/de/ipb_halle/lbac/base/ItemCreator.java index b69e9af69..27ef7fd95 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/ItemCreator.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/ItemCreator.java @@ -17,9 +17,9 @@ */ package de.ipb_halle.lbac.base; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.container.Container; import de.ipb_halle.lbac.project.Project; +import de.ipb_halle.test.EntityManagerService; import java.util.UUID; /** diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/MaterialCreator.java b/ui/src/test/java/de/ipb_halle/lbac/base/MaterialCreator.java index 043edf3ae..aee790c4a 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/MaterialCreator.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/MaterialCreator.java @@ -17,9 +17,9 @@ */ package de.ipb_halle.lbac.base; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.material.MaterialType; +import de.ipb_halle.test.EntityManagerService; /** * diff --git a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java index 66552c5ce..71c993436 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java +++ b/ui/src/test/java/de/ipb_halle/lbac/base/TestBase.java @@ -19,7 +19,6 @@ import de.ipb_halle.kx.file.FileObjectService; import de.ipb_halle.kx.termvector.TermVectorService; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.admission.AdmissionSubSystemType; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.ACList; @@ -46,10 +45,10 @@ import de.ipb_halle.lbac.admission.MemberService; import de.ipb_halle.lbac.admission.MembershipService; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.lbac.reporting.mocks.ReportsDirectoryMock; import de.ipb_halle.lbac.service.NodeService; import de.ipb_halle.scope.SessionScopeContext; import de.ipb_halle.scope.SessionScopeResetEvent; +import de.ipb_halle.test.EntityManagerService; import java.io.Serializable; import java.net.URL; @@ -149,7 +148,6 @@ public class TestBase implements Serializable { public static WebArchive prepareDeployment(String archiveName) { WebArchive archive = ShrinkWrap.create(WebArchive.class, archiveName) .addClass(GlobalAdmissionContextMock.class) - .addClass(ReportsDirectoryMock.class) .addClass(GlobalVersions.class) .addClass(ACListService.class) .addClass(CloudService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/container/service/ContainerServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/container/service/ContainerServiceTest.java index c2f1b015e..c34b69220 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/container/service/ContainerServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/container/service/ContainerServiceTest.java @@ -17,14 +17,13 @@ */ package de.ipb_halle.lbac.container.service; -import de.ipb_halle.lbac.EntityManagerService; -import de.ipb_halle.lbac.admission.GlobalAdmissionContext; -import de.ipb_halle.lbac.admission.UserBeanDeployment; -import de.ipb_halle.lbac.base.TestBase; import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACPermission; +import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; +import de.ipb_halle.lbac.admission.UserBeanDeployment; +import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.container.Container; import de.ipb_halle.lbac.container.ContainerType; import de.ipb_halle.lbac.items.Item; @@ -39,6 +38,7 @@ import de.ipb_halle.lbac.material.common.service.MaterialService; import de.ipb_halle.lbac.material.mocks.StructureInformationSaverMock; import de.ipb_halle.lbac.material.structure.Structure; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.util.ArrayList; import java.util.Date; @@ -49,8 +49,8 @@ import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.jupiter.api.AfterEach; import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/ui/src/test/java/de/ipb_halle/lbac/database/SQLFunctionsTest.java b/ui/src/test/java/de/ipb_halle/lbac/database/SQLFunctionsTest.java index 656777801..1570c8401 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/database/SQLFunctionsTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/database/SQLFunctionsTest.java @@ -26,13 +26,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.container.Container; import de.ipb_halle.lbac.container.ContainerType; import de.ipb_halle.lbac.container.service.ContainerService; import de.ipb_halle.lbac.items.ItemDeployment; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** @@ -128,4 +128,4 @@ private String getDimensionLabel(Boolean zerobased, Boolean swapdimension, Integ String query = String.format(GET_DIMENSION_LABEL_FORMAT, zerobased, swapdimension, itemrow, itemcol); return (String) ems.doSqlQuery(query).get(0); } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/device/print/PrintBeanDeployment.java b/ui/src/test/java/de/ipb_halle/lbac/device/print/PrintBeanDeployment.java index e96672727..269a5cf75 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/device/print/PrintBeanDeployment.java +++ b/ui/src/test/java/de/ipb_halle/lbac/device/print/PrintBeanDeployment.java @@ -17,7 +17,8 @@ */ package de.ipb_halle.lbac.device.print; -import de.ipb_halle.lbac.device.job.JobService; +import de.ipb_halle.job.JobService; +import de.ipb_halle.lbac.device.job.PrintJobService; import de.ipb_halle.lbac.util.pref.PreferenceService; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -29,10 +30,11 @@ public class PrintBeanDeployment { public static WebArchive add(WebArchive deployment) { - return deployment.addClass(JobService.class) + return deployment.addClass(PrintJobService.class) .addClass(LabelService.class) .addClass(PrintBean.class) .addClass(PrinterService.class) - .addClass(PreferenceService.class); + .addClass(PreferenceService.class) + .addClass(JobService.class); } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/device/print/PrinterTest.java b/ui/src/test/java/de/ipb_halle/lbac/device/print/PrinterTest.java index c5e439d3c..164a8792f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/device/print/PrinterTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/device/print/PrinterTest.java @@ -17,37 +17,35 @@ */ package de.ipb_halle.lbac.device.print; +import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; +import static org.junit.Assert.assertEquals; + +import de.ipb_halle.job.JobService; +import de.ipb_halle.job.JobStatus; +import de.ipb_halle.job.JobType; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.admission.mock.UserBeanMock; import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; -import de.ipb_halle.lbac.device.job.JobStatus; -import de.ipb_halle.lbac.device.job.JobType; -import static de.ipb_halle.lbac.base.TestBase.prepareDeployment; +import de.ipb_halle.lbac.device.job.PrintJob; +import de.ipb_halle.lbac.device.job.PrintJobService; import de.ipb_halle.lbac.util.pref.Preference; import de.ipb_halle.lbac.util.pref.PreferenceService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; - import java.util.HashMap; import java.util.Map; import java.util.List; - import javax.faces.model.SelectItem; import javax.inject.Inject; - import org.jboss.arquillian.junit5.ArquillianExtension; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Assert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; -import static org.junit.Assert.assertEquals; /** * This test class covers substantial code portions of the job, label and @@ -64,7 +62,7 @@ public class PrinterTest extends TestBase { private PrintBean printBean; @Inject - private JobService jobService; + private PrintJobService jobService; @Inject private LabelService labelService; @@ -173,7 +171,7 @@ public void testPrinting() { cmap.put(JobService.CONDITION_QUEUE, queue); cmap.put(JobService.CONDITION_STATUS, JobStatus.PENDING); cmap.put(JobService.CONDITION_JOBTYPE, JobType.PRINT); - List jobs = this.jobService.loadJobs(cmap); + List jobs = this.jobService.loadJobs(cmap); assertEquals("testPrinting() active job count", 1, jobs.size()); assertEquals("testPrinting() job queue", queue, jobs.get(0).getQueue()); diff --git a/ui/src/test/java/de/ipb_halle/lbac/forum/ForumServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/forum/ForumServiceTest.java index 435f0027e..452184b61 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/forum/ForumServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/forum/ForumServiceTest.java @@ -17,7 +17,6 @@ */ package de.ipb_halle.lbac.forum; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.entity.Cloud; @@ -29,6 +28,7 @@ import de.ipb_halle.lbac.globals.KeyManager; import de.ipb_halle.lbac.admission.MembershipService; import de.ipb_halle.lbac.entity.Node; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.util.ArrayList; import java.util.Date; diff --git a/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java b/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java index f5b988c39..b779b930f 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java +++ b/ui/src/test/java/de/ipb_halle/lbac/items/ItemDeployment.java @@ -20,7 +20,7 @@ import de.ipb_halle.lbac.container.service.ContainerNestingService; import de.ipb_halle.lbac.container.service.ContainerPositionService; import de.ipb_halle.lbac.container.service.ContainerService; -import de.ipb_halle.lbac.device.job.JobService; +import de.ipb_halle.lbac.device.job.PrintJobService; import de.ipb_halle.lbac.device.print.LabelService; import de.ipb_halle.lbac.device.print.PrintBean; import de.ipb_halle.lbac.device.print.PrinterService; @@ -31,11 +31,11 @@ import de.ipb_halle.lbac.items.service.ItemService; import de.ipb_halle.lbac.material.MaterialDeployment; import de.ipb_halle.lbac.material.common.service.MaterialService; -import de.ipb_halle.lbac.reporting.job.ReportJobService; -import de.ipb_halle.lbac.reporting.report.ReportMgr; +import de.ipb_halle.lbac.reporting.ReportJobService; +import de.ipb_halle.lbac.reporting.ReportMgr; import de.ipb_halle.lbac.util.pref.PreferenceService; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; -import de.ipb_halle.reporting.report.ReportService; +import de.ipb_halle.reporting.ReportService; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -56,7 +56,7 @@ public static WebArchive add(WebArchive deployment) { .addClass(ItemBean.class) .addClass(ItemLabelService.class) .addClass(PrintBean.class) - .addClass(JobService.class) + .addClass(PrintJobService.class) .addClass(PrinterService.class) .addClass(LabelService.class) .addClass(PreferenceService.class) diff --git a/ui/src/test/java/de/ipb_halle/lbac/items/service/ItemServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/items/service/ItemServiceTest.java index 58b1561e1..b1a5a3553 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/items/service/ItemServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/items/service/ItemServiceTest.java @@ -17,22 +17,21 @@ */ package de.ipb_halle.lbac.items.service; -import de.ipb_halle.lbac.container.service.ContainerService; -import de.ipb_halle.lbac.EntityManagerService; -import de.ipb_halle.lbac.admission.GlobalAdmissionContext; -import de.ipb_halle.lbac.admission.UserBeanDeployment; -import de.ipb_halle.lbac.base.TestBase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import de.ipb_halle.lbac.admission.GlobalAdmissionContext; import de.ipb_halle.lbac.admission.User; +import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.container.Container; import de.ipb_halle.lbac.container.ContainerType; import de.ipb_halle.lbac.container.service.ContainerPositionService; +import de.ipb_halle.lbac.container.service.ContainerService; import de.ipb_halle.lbac.admission.ACList; import de.ipb_halle.lbac.admission.ACPermission; +import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.items.Item; import de.ipb_halle.lbac.items.ItemDeployment; import de.ipb_halle.lbac.items.ItemDifference; @@ -49,6 +48,7 @@ import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.lbac.util.units.Quantity; import de.ipb_halle.lbac.util.units.Unit; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.util.ArrayList; import java.util.Date; diff --git a/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java b/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java index 673025640..310e10025 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java +++ b/ui/src/test/java/de/ipb_halle/lbac/material/MaterialDeployment.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.material; -import de.ipb_halle.lbac.device.job.JobService; +import de.ipb_halle.lbac.device.job.PrintJobService; import de.ipb_halle.lbac.material.biomaterial.TaxonomyNestingService; import de.ipb_halle.lbac.material.biomaterial.TaxonomyService; import de.ipb_halle.lbac.material.biomaterial.TissueService; @@ -29,10 +29,10 @@ import de.ipb_halle.lbac.material.sequence.search.service.SearchParameterService; import de.ipb_halle.lbac.material.sequence.search.service.SequenceSearchService; import de.ipb_halle.lbac.project.ProjectService; -import de.ipb_halle.lbac.reporting.job.ReportJobService; -import de.ipb_halle.lbac.reporting.report.ReportMgr; +import de.ipb_halle.lbac.reporting.ReportJobService; +import de.ipb_halle.lbac.reporting.ReportMgr; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; -import de.ipb_halle.reporting.report.ReportService; +import de.ipb_halle.reporting.ReportService; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -58,7 +58,7 @@ public static WebArchive add(WebArchive deployment) { .addClass(ReportMgr.class) .addClass(ReportService.class) .addClass(ReportJobService.class) - .addClass(JobService.class) + .addClass(PrintJobService.class) .addClass(SendFileBeanMock.class) .addClass(TaxonomyNestingService.class); } diff --git a/ui/src/test/java/de/ipb_halle/lbac/projects/ProjectServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/projects/ProjectServiceTest.java index d44a2c67d..960cde6bd 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/projects/ProjectServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/projects/ProjectServiceTest.java @@ -34,7 +34,9 @@ import de.ipb_halle.lbac.project.ProjectType; import de.ipb_halle.lbac.search.SearchResult; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.inject.Inject; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; @@ -214,13 +216,23 @@ public void test005_getSimilarProjectNames() { } private void cleanUp(User user2, ACList projectAcList, Project p) { + Map params = new HashMap<> (); entityManagerService.doSqlUpdate("delete from projecttemplates"); entityManagerService.doSqlUpdate("delete from budgetreservations"); - entityManagerService.doSqlUpdate("delete from acentries where aclist_id=" + projectAcList.getId().toString()); - entityManagerService.doSqlUpdate("delete from projects where id=" + p.getId()); - entityManagerService.doSqlUpdate("delete from aclists where id=" + projectAcList.getId().toString()); - entityManagerService.deleteUserWithAllMemberships(user2.getId().toString()); - entityManagerService.doSqlUpdate("delete from usersgroups where name='" + user2.getName() + "'"); + params.put("aclist", projectAcList.getId()); + entityManagerService.doSqlUpdate("delete from acentries where aclist_id=:aclist", params); + params.clear(); + params.put("id", p.getId()); + entityManagerService.doSqlUpdate("delete from projects where id=:id", params); + params.clear(); + params.put("aclist", projectAcList.getId()); + entityManagerService.doSqlUpdate("delete from aclists where id=:aclist", params); + params.clear(); + params.put("id", user2.getId()); + entityManagerService.doSqlUpdate("delete from memberships where member_id=:id", params); + params.clear(); + params.put("name", user2.getName()); + entityManagerService.doSqlUpdate("delete from usersgroups where name=:name", params); } @Deployment diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobServiceTest.java new file mode 100644 index 000000000..183b46cb5 --- /dev/null +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobServiceTest.java @@ -0,0 +1,244 @@ +/* + * Cloud Resource & Information Management System (CRIMSy) + * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie + * + * 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.ipb_halle.lbac.reporting; + +import static de.ipb_halle.job.JobService.CONDITION_JOBTYPE; +import static de.ipb_halle.job.JobService.CONDITION_STATUS; +import static de.ipb_halle.job.JobStatus.BUSY; +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobStatus.FAILED; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobType.REPORT; +import static de.ipb_halle.lbac.reporting.ReportJobService.MAX_AGE; +import static de.ipb_halle.reporting.ReportType.CSV; +import static de.ipb_halle.reporting.ReportType.PDF; +import static de.ipb_halle.reporting.ReportType.XLSX; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import de.ipb_halle.job.JobService; +import de.ipb_halle.job.JobStatus; +import de.ipb_halle.lbac.base.TestBase; +import de.ipb_halle.lbac.reporting.mocks.ReportsDirectoryMock; +import de.ipb_halle.reporting.ReportDataPojo; +import de.ipb_halle.test.ManagedExecutorServiceMock; +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +/** + * @author flange + */ +@ExtendWith(PostgresqlContainerExtension.class) +@ExtendWith(ArquillianExtension.class) +public class ReportJobServiceTest extends TestBase { + private static final long serialVersionUID = 1L; + + @Inject + private ReportJobService jobService; + + @Inject + private ReportsDirectoryMock reportsDirectory; + + @Deployment + public static WebArchive createDeployment() { + return prepareDeployment("ReportJobServiceTest.war") + .addClass(ReportJobService.class) + .addClass(ReportsDirectoryMock.class) + .addClass(JobService.class); + } + + + @Test + public void test_submit() { + Map params = new HashMap<>(); + params.put("abc", "def"); + + jobService.submit(new ReportDataPojo("report1 (pending)", CSV, params), adminUser); + jobService.saveJob(jobService.submit(new ReportDataPojo("report2 (busy)", + PDF, new HashMap<>()), adminUser).setStatus(BUSY)); + jobService.saveJob(jobService.submit(new ReportDataPojo("report3 (completed)", + XLSX, null), adminUser).setStatus(COMPLETED)); + jobService.saveJob(jobService.submit(new ReportDataPojo("report4 (failed)", + CSV, null), adminUser).setStatus(FAILED)); + + List allJobs = allJobs(); + List busyJobs = jobsByStatus(BUSY); + List pendingJobs = jobsByStatus(PENDING); + assertThat(allJobs, hasSize(4)); + assertThat(busyJobs, hasSize(1)); + assertThat(pendingJobs, hasSize(1)); + + ReportDataPojo pojo = ReportDataPojo.deserialize(pendingJobs.get(0).getInput()); + assertEquals(adminUser.getId(), pendingJobs.get(0).getOwner().getId()); + assertEquals("report1 (pending)", pojo.getReportURI()); + assertEquals(CSV, pojo.getType()); + assertEquals(params, pojo.getParameters()); + + pojo = ReportDataPojo.deserialize(busyJobs.get(0).getInput()); + assertEquals("report2 (busy)", pojo.getReportURI()); + assertEquals(PDF, pojo.getType()); + assertTrue(pojo.getParameters().isEmpty()); + + pojo = ReportDataPojo.deserialize(jobsByStatus(COMPLETED).get(0).getInput()); + assertEquals("report3 (completed)", pojo.getReportURI()); + assertEquals(XLSX, pojo.getType()); + assertNull(pojo.getParameters()); + + pojo = ReportDataPojo.deserialize(jobsByStatus(FAILED).get(0).getInput()); + assertEquals("report4 (failed)", pojo.getReportURI()); + assertEquals(CSV, pojo.getType()); + assertNull(pojo.getParameters()); + } + + + @Test + public void test_loadJobsForUser() { + ReportJob job1 = new ReportJob().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); + ReportJob job2 = new ReportJob().setJobType(REPORT).setStatus(PENDING).setOwner(publicUser).setQueue(""); + job1 = jobService.saveJob(job1); + job2 = jobService.saveJob(job2); + + List allJobs = allJobs(); + List jobs = jobService.loadJobsForUser(adminUser); + + assertThat(allJobs, hasSize(2)); + assertThat(jobs, hasSize(1)); + assertEquals(job1.getJobId(), jobs.get(0).getJobId()); + } + + @Test + public void test_deleteJob_fileExists() throws IOException { + File tempFile = File.createTempFile("ReportJobServiceTest", "test"); + tempFile.deleteOnExit(); + assertTrue(tempFile.exists()); + + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") + .setOutput(tempFile.getAbsolutePath().getBytes()); + job = jobService.saveJob(job); + + jobService.deleteJob(job); + + assertThat(allJobs(), is(empty())); + assertFalse(tempFile.exists()); + } + + @Test + public void test_deleteJob_fileDoesNotExist(@TempDir File tempDir) { + String nonExistingFilename = tempDir.getAbsolutePath() + "/doesNotExist.file"; + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") + .setOutput(nonExistingFilename.getBytes()); + job = jobService.saveJob(job); + + jobService.deleteJob(job); + + assertThat(allJobs(), is(empty())); + } + + @Test + public void test_deleteJob_jobOutputIsNull() { + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("").setOutput(null); + job = jobService.saveJob(job); + + jobService.deleteJob(job); + + assertThat(allJobs(), is(empty())); + } + + @Test + public void test_getOutputFileOfJob_noJobInDB() { + ReportJob job = new ReportJob().setJobId(-1); + + assertNull(jobService.getOutputFileOfJob(job)); + } + + @Test + public void test_getOutputFileOfJob_jobOutputIsNull() { + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("").setOutput(null); + job = jobService.saveJob(job); + + assertNull(jobService.getOutputFileOfJob(job)); + } + + @Test + public void test_getOutputFileOfJob_fileDoesNotExist(@TempDir File tempDir) { + String nonExistingFilename = tempDir.getAbsolutePath() + "/doesNotExist.file"; + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") + .setOutput(nonExistingFilename.getBytes()); + job = jobService.saveJob(job); + + assertNull(jobService.getOutputFileOfJob(job)); + } + + @Test + public void test_getOutputFileOfJob_fileExists() throws IOException { + File tempFile = File.createTempFile("ReportJobServiceTest", "test2"); + tempFile.deleteOnExit(); + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") + .setOutput(tempFile.getAbsolutePath().getBytes()); + job = jobService.saveJob(job); + + File f = jobService.getOutputFileOfJob(job); + + assertNotNull(f); + assertEquals(tempFile, f); + } + + private File createTempFileInReportsDirectory() throws IOException { + File reportsDir = new File(reportsDirectory.getReportsDirectory()); + File tempFile = File.createTempFile("ReportJobServiceTest", "test", reportsDir); + tempFile.deleteOnExit(); + return tempFile; + } + + private List allJobs() { + Map cmap = new HashMap<>(); + cmap.put(CONDITION_JOBTYPE, REPORT); + return jobService.loadJobs(cmap); + } + + private List jobsByStatus(JobStatus status) { + Map cmap = new HashMap<>(); + cmap.put(CONDITION_JOBTYPE, REPORT); + cmap.put(CONDITION_STATUS, status); + return jobService.loadJobs(cmap); + } +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapperTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobTest.java similarity index 62% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapperTest.java rename to ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobTest.java index d745ed223..84da6a064 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobWapperTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportJobTest.java @@ -15,93 +15,84 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.jobview; +package de.ipb_halle.lbac.reporting; -import static de.ipb_halle.lbac.device.job.JobStatus.COMPLETED; -import static de.ipb_halle.lbac.device.job.JobStatus.FAILED; -import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobStatus.FAILED; +import static de.ipb_halle.job.JobStatus.PENDING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import de.ipb_halle.job.JobStatus; import java.io.File; import java.io.IOException; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobStatus; -public class ReportingJobWapperTest { - private Job job; - private ReportingJobWapper wrapper; +public class ReportJobTest { + private ReportJob job; @BeforeEach public void before() { - job = new Job(); - wrapper = new ReportingJobWapper(job); - } - - @Test - public void test_getJob() { - assertSame(job, wrapper.getJob()); + job = new ReportJob(); } @Test public void test_getI18nKeyForStatus() { job.setStatus(null); - assertEquals("", wrapper.getI18nKeyForStatus()); + assertEquals("", job.getI18nKeyForStatus()); for (JobStatus status : JobStatus.values()) { job.setStatus(status); - assertEquals("jobStatus_" + status.toString(), wrapper.getI18nKeyForStatus()); + assertEquals("jobStatus_" + status.toString(), job.getI18nKeyForStatus()); } } @Test public void test_isDownloadable(@TempDir File tempDir) throws IOException { job.setStatus(FAILED); - assertFalse(wrapper.isDownloadable()); + assertFalse(job.isDownloadable()); job.setStatus(COMPLETED).setOutput(null); - assertFalse(wrapper.isDownloadable()); + assertFalse(job.isDownloadable()); String nonExistingFilename = tempDir.getAbsolutePath() + "/doesNotExist.file"; job.setOutput(nonExistingFilename.getBytes()); - assertFalse(wrapper.isDownloadable()); + assertFalse(job.isDownloadable()); - File tempFile = File.createTempFile("ReportingJobWapperTest", "test", tempDir); + File tempFile = File.createTempFile("ReportJobTest", "test", tempDir); tempFile.deleteOnExit(); job.setOutput(tempFile.getAbsolutePath().getBytes()); - assertTrue(wrapper.isDownloadable()); + assertTrue(job.isDownloadable()); } @Test public void test_isDeleteable() { job.setStatus(null); - assertFalse(wrapper.isDeleteable()); + assertFalse(job.isDeleteable()); job.setStatus(PENDING); - assertFalse(wrapper.isDeleteable()); + assertFalse(job.isDeleteable()); job.setStatus(FAILED); - assertTrue(wrapper.isDeleteable()); + assertTrue(job.isDeleteable()); job.setStatus(COMPLETED); - assertTrue(wrapper.isDeleteable()); + assertTrue(job.isDeleteable()); } @Test public void test_getRowStyleClass() { job.setStatus(null); - assertEquals("", wrapper.getRowStyleClass()); + assertEquals("", job.getRowStyleClass()); for (JobStatus status : JobStatus.values()) { job.setStatus(status); - assertEquals("report-" + status.toString().toLowerCase(), wrapper.getRowStyleClass()); + assertEquals("report-" + status.toString().toLowerCase(), job.getRowStyleClass()); } } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportMgrTest.java similarity index 70% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java rename to ui/src/test/java/de/ipb_halle/lbac/reporting/ReportMgrTest.java index d77b897a4..b5e29c327 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/report/ReportMgrTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportMgrTest.java @@ -15,16 +15,26 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.report; +package de.ipb_halle.lbac.reporting; -import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; -import static de.ipb_halle.lbac.reporting.report.ReportMgr.DEFAULT_NAME; -import static de.ipb_halle.reporting.report.ReportType.PDF; +import static de.ipb_halle.job.JobStatus.PENDING; +import static de.ipb_halle.lbac.reporting.ReportMgr.DEFAULT_NAME; +import static de.ipb_halle.reporting.ReportType.PDF; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; + +import de.ipb_halle.job.JobService; +import de.ipb_halle.lbac.base.TestBase; +import de.ipb_halle.lbac.reporting.ReportJobService; +import de.ipb_halle.reporting.Report; +import de.ipb_halle.reporting.ReportDataPojo; +import de.ipb_halle.reporting.ReportEntity; +import de.ipb_halle.reporting.ReportService; +import de.ipb_halle.reporting.ReportType; +import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; @@ -32,9 +42,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.inject.Inject; - import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -42,17 +50,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; -import de.ipb_halle.test.ManagedExecutorServiceMock; -import de.ipb_halle.lbac.reporting.job.ReportJobPojo; -import de.ipb_halle.lbac.reporting.job.ReportJobService; -import de.ipb_halle.reporting.report.Report; -import de.ipb_halle.reporting.report.ReportEntity; -import de.ipb_halle.reporting.report.ReportService; -import de.ipb_halle.reporting.report.ReportType; -import de.ipb_halle.testcontainers.PostgresqlContainerExtension; /** * @author flange @@ -74,14 +71,6 @@ public static WebArchive createDeployment() { ReportJobService.class, JobService.class); } - private ManagedExecutorServiceMock managedExecutorService; - - @BeforeEach - private void before() { - managedExecutorService = new ManagedExecutorServiceMock(2); - reportJobService.setManagedExecutorService(managedExecutorService); - } - @Test public void test_getAvailableReports() { insertReport("context1", "no{Valid:Json}", "source1a"); @@ -124,28 +113,14 @@ public void test_submitReport() { reportMgr.submitReport(report, params, PDF, adminUser); // reporting job arrived in the database - List jobs = reportJobService.loadJobsForUser(adminUser); + List jobs = reportJobService.loadJobsForUser(adminUser); assertThat(jobs, hasSize(1)); - Job job = jobs.get(0); - ReportJobPojo pojo = deserializeToReportJobPojo(job.getInput()); - assertEquals(BUSY, job.getStatus()); + ReportJob job = jobs.get(0); + ReportDataPojo pojo = ReportDataPojo.deserialize(job.getInput()); + assertEquals(PENDING, job.getStatus()); assertEquals("ghi", pojo.getReportURI()); assertEquals(PDF, pojo.getType()); assertEquals(params, pojo.getParameters()); - - // reporting job was submitted to the managedExecutorService - assertThat(managedExecutorService.getSubmittedTasks(), hasSize(1)); - } - - private ReportJobPojo deserializeToReportJobPojo(byte[] bytes) { - Object o = null; - try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(bais)) { - o = ois.readObject(); - } catch (ClassNotFoundException | IOException e) { - throw new RuntimeException(e); - } - return (ReportJobPojo) o; } } diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobsBeanTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportingJobsBeanTest.java similarity index 78% rename from ui/src/test/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobsBeanTest.java rename to ui/src/test/java/de/ipb_halle/lbac/reporting/ReportingJobsBeanTest.java index c8786ef69..699b62c80 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/jobview/ReportingJobsBeanTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/ReportingJobsBeanTest.java @@ -15,11 +15,11 @@ * limitations under the License. * */ -package de.ipb_halle.lbac.reporting.jobview; +package de.ipb_halle.lbac.reporting; -import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; -import static de.ipb_halle.lbac.device.job.JobStatus.COMPLETED; -import static de.ipb_halle.lbac.device.job.JobType.REPORT; +import static de.ipb_halle.job.JobStatus.BUSY; +import static de.ipb_halle.job.JobStatus.COMPLETED; +import static de.ipb_halle.job.JobType.REPORT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -40,13 +40,12 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import de.ipb_halle.job.JobService; import de.ipb_halle.lbac.admission.UserBeanDeployment; import de.ipb_halle.lbac.admission.mock.UserBeanMock; import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; -import de.ipb_halle.lbac.device.job.JobStatus; -import de.ipb_halle.lbac.reporting.job.ReportJobService; +import de.ipb_halle.job.JobStatus; +import de.ipb_halle.lbac.reporting.ReportJobService; import de.ipb_halle.lbac.util.jsf.SendFileBeanMock; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; @@ -68,7 +67,7 @@ public class ReportingJobsBeanTest extends TestBase { private SendFileBeanMock sendFileBeanMock;; @Inject - private JobService jobService; + private ReportJobService jobService; @Deployment public static WebArchive createDeployment() { @@ -89,15 +88,15 @@ public void test_actionReloadTable() { assertThat(bean.getReportingJobs(), is(empty())); - Job job1 = new Job().setJobType(REPORT).setStatus(BUSY).setOwner(adminUser).setQueue(""); - Job job2 = new Job().setJobType(REPORT).setStatus(BUSY).setOwner(publicUser).setQueue(""); + ReportJob job1 = new ReportJob().setJobType(REPORT).setStatus(BUSY).setOwner(adminUser).setQueue(""); + ReportJob job2 = new ReportJob().setJobType(REPORT).setStatus(BUSY).setOwner(publicUser).setQueue(""); job1 = jobService.saveJob(job1); job2 = jobService.saveJob(job2); bean.actionReloadTable(); assertThat(bean.getReportingJobs(), hasSize(1)); - assertEquals(job1.getJobId(), bean.getReportingJobs().get(0).getJob().getJobId()); + assertEquals(job1.getJobId(), bean.getReportingJobs().get(0).getJobId()); } @Test @@ -105,11 +104,11 @@ public void test_actionDownloadReport(@TempDir File tempDir) throws IOException File tempFile = File.createTempFile("ReportingJobsBeanTest", "test", tempDir); tempFile.deleteOnExit(); FileUtils.write(tempFile, "abc"); - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") .setOutput(tempFile.getAbsolutePath().getBytes()); job = jobService.saveJob(job); - bean.actionDownloadReport(new ReportingJobWapper(job)); + bean.actionDownloadReport(job); assertEquals("abc", new String(sendFileBeanMock.getContent())); assertEquals(tempFile.getName(), sendFileBeanMock.getFilename()); @@ -119,11 +118,11 @@ public void test_actionDownloadReport(@TempDir File tempDir) throws IOException public void test_actionDeleteReport(@TempDir File tempDir) throws IOException { File tempFile = File.createTempFile("ReportingJobsBeanTest", "test2", tempDir); tempFile.deleteOnExit(); - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") + ReportJob job = new ReportJob().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") .setOutput(tempFile.getAbsolutePath().getBytes()); job = jobService.saveJob(job); - bean.actionDeleteReport(new ReportingJobWapper(job)); + bean.actionDeleteReport(job); assertThat(bean.getReportingJobs(), is(empty())); assertFalse(tempFile.exists()); @@ -131,12 +130,11 @@ public void test_actionDeleteReport(@TempDir File tempDir) throws IOException { @Test public void test_getJobStatusI18n() { - Job job = new Job(); - ReportingJobWapper wrapper = new ReportingJobWapper(job); + ReportJob job = new ReportJob(); for (JobStatus status : JobStatus.values()) { job.setStatus(status); - assertEquals("jobStatus_" + status.toString(), bean.getJobStatusI18n(wrapper)); + assertEquals("jobStatus_" + status.toString(), bean.getJobStatusI18n(job)); } } -} \ No newline at end of file +} diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java deleted file mode 100644 index 7c7eabb1e..000000000 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/job/ReportJobServiceTest.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Cloud Resource & Information Management System (CRIMSy) - * Copyright 2022 Leibniz-Institut f. Pflanzenbiochemie - * - * 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.ipb_halle.lbac.reporting.job; - -import de.ipb_halle.reporting.report.ReportsDirectory; -import de.ipb_halle.test.ManagedExecutorServiceMock; -import static de.ipb_halle.lbac.device.job.JobService.CONDITION_JOBTYPE; -import static de.ipb_halle.lbac.device.job.JobService.CONDITION_STATUS; -import static de.ipb_halle.lbac.device.job.JobStatus.BUSY; -import static de.ipb_halle.lbac.device.job.JobStatus.COMPLETED; -import static de.ipb_halle.lbac.device.job.JobStatus.FAILED; -import static de.ipb_halle.lbac.device.job.JobStatus.PENDING; -import static de.ipb_halle.lbac.device.job.JobType.REPORT; -import static de.ipb_halle.lbac.reporting.job.ReportJobService.MAX_AGE; -import static de.ipb_halle.reporting.report.ReportType.CSV; -import static de.ipb_halle.reporting.report.ReportType.PDF; -// xxxxx re-enable XLSX!!! -//import static de.ipb_halle.lbac.reporting.report.ReportType.XLSX; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.time.Instant; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit5.ArquillianExtension; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; - -import de.ipb_halle.lbac.base.TestBase; -import de.ipb_halle.lbac.device.job.Job; -import de.ipb_halle.lbac.device.job.JobService; -import de.ipb_halle.testcontainers.PostgresqlContainerExtension; - -/** - * @author flange - */ -@ExtendWith(PostgresqlContainerExtension.class) -@ExtendWith(ArquillianExtension.class) -public class ReportJobServiceTest extends TestBase { - private static final long serialVersionUID = 1L; - - @Inject - private ReportJobService reportJobService; - - @Inject - private JobService jobService; - - @Inject - private ReportsDirectory reportsDirectory; - - @Deployment - public static WebArchive createDeployment() { - return prepareDeployment("ReportJobServiceTest.war").addClasses(ReportJobService.class, JobService.class); - } - - private ManagedExecutorServiceMock managedExecutorService; - - @BeforeEach - private void before() { - managedExecutorService = new ManagedExecutorServiceMock(2); - reportJobService.setManagedExecutorService(managedExecutorService); - } - - @Test - public void test_submit() { - Map params = new HashMap<>(); - params.put("abc", "def"); - - reportJobService.submit(new ReportJobPojo("report1 (busy)", CSV, params), adminUser); - reportJobService.submit(new ReportJobPojo("report2 (busy)", PDF, new HashMap<>()), adminUser); -// reportJobService.submit(new ReportJobPojo("report3 (pending)", XLSX, null), adminUser); - reportJobService.submit(new ReportJobPojo("report3 (pending)", CSV, null), adminUser); - - assertThat(managedExecutorService.getSubmittedTasks(), hasSize(2)); - - List allJobs = allJobs(); - List busyJobs = busyJobs(); - List pendingJobs = pendingJobs(); - assertThat(allJobs, hasSize(3)); - assertThat(busyJobs, hasSize(2)); - assertThat(pendingJobs, hasSize(1)); - - ReportJobPojo pojo = deserializeToReportJobPojo(busyJobs.get(0).getInput()); - assertEquals("report1 (busy)", pojo.getReportURI()); - assertEquals(CSV, pojo.getType()); - assertEquals(params, pojo.getParameters()); - - pojo = deserializeToReportJobPojo(busyJobs.get(1).getInput()); - assertEquals("report2 (busy)", pojo.getReportURI()); - assertEquals(PDF, pojo.getType()); - assertTrue(pojo.getParameters().isEmpty()); - - pojo = deserializeToReportJobPojo(pendingJobs.get(0).getInput()); - assertEquals("report3 (pending)", pojo.getReportURI()); -// assertEquals(XLSX, pojo.getType()); - assertEquals(CSV, pojo.getType()); // XLSX temporarily removed - assertNull(pojo.getParameters()); - } - - @Test - public void test_markBusyJobsAsPending() { - Job job = new Job().setJobType(REPORT).setStatus(BUSY).setOwner(adminUser).setQueue(""); - job = jobService.saveJob(job); - - reportJobService.markBusyJobsAsPending(); - - job = jobService.loadJobById(job.getJobId()); - assertEquals(PENDING, job.getStatus()); - } - - @Test - public void test_submitPendingTasksToExecutor() { - Job pending1 = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); - Job pending2 = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); - Job pending3 = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); - pending1 = jobService.saveJob(pending1); - pending2 = jobService.saveJob(pending2); - pending3 = jobService.saveJob(pending3); - - reportJobService.submitPendingTasksToExecutor(); - - assertThat(managedExecutorService.getSubmittedTasks(), hasSize(2)); - - List allJobs = allJobs(); - List busyJobs = busyJobs(); - List pendingJobs = pendingJobs(); - assertThat(allJobs, hasSize(3)); - assertThat(busyJobs, hasSize(2)); - assertThat(pendingJobs, hasSize(1)); - - assertEquals(pending1.getJobId(), busyJobs.get(0).getJobId()); - assertEquals(pending2.getJobId(), busyJobs.get(1).getJobId()); - assertEquals(pending3.getJobId(), pendingJobs.get(0).getJobId()); - } - - @Test - public void test_markJobAsCompleted_withValidJobId() { - Job pendingJob = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); - pendingJob = jobService.saveJob(pendingJob); - - Job completedJob = reportJobService.markJobAsCompleted(pendingJob.getJobId(), "somewhere"); - - assertEquals(COMPLETED, completedJob.getStatus()); - assertEquals("somewhere", new String(completedJob.getOutput())); - - // everything correct in the database? - List allJobs = allJobs(); - assertThat(allJobs, hasSize(1)); - assertEquals(COMPLETED, allJobs.get(0).getStatus()); - assertEquals("somewhere", new String(allJobs.get(0).getOutput())); - } - - @Test - public void test_markJobAsCompleted_withInvalidJobId() { - assertNull(reportJobService.markJobAsCompleted(42, "somewhere")); - - assertThat(allJobs(), is(empty())); - } - - @Test - public void test_markJobAsFailed_withValidJobId() { - Job pendingJob = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); - pendingJob = jobService.saveJob(pendingJob); - - Job failedJob = reportJobService.markJobAsFailed(pendingJob.getJobId()); - - assertEquals(FAILED, failedJob.getStatus()); - assertNull(failedJob.getOutput()); - - // everything correct in the database? - List allJobs = allJobs(); - assertThat(allJobs, hasSize(1)); - assertEquals(FAILED, allJobs.get(0).getStatus()); - assertNull(allJobs.get(0).getOutput()); - } - - @Test - public void test_markJobAsFailed_withInvalidJobId() { - assertNull(reportJobService.markJobAsFailed(42)); - - assertThat(allJobs(), is(empty())); - } - - @Test - public void test_loadJobsForUser() { - Job job1 = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(adminUser).setQueue(""); - Job job2 = new Job().setJobType(REPORT).setStatus(PENDING).setOwner(publicUser).setQueue(""); - job1 = jobService.saveJob(job1); - job2 = jobService.saveJob(job2); - - List jobs = reportJobService.loadJobsForUser(adminUser); - - assertThat(jobs, hasSize(1)); - assertEquals(job1.getJobId(), jobs.get(0).getJobId()); - } - - @Test - public void test_deleteJob_fileExists() throws IOException { - File tempFile = File.createTempFile("ReportJobServiceTest", "test"); - tempFile.deleteOnExit(); - assertTrue(tempFile.exists()); - - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") - .setOutput(tempFile.getAbsolutePath().getBytes()); - job = jobService.saveJob(job); - - reportJobService.deleteJob(job); - - assertThat(allJobs(), is(empty())); - assertFalse(tempFile.exists()); - } - - @Test - public void test_deleteJob_fileDoesNotExist(@TempDir File tempDir) { - String nonExistingFilename = tempDir.getAbsolutePath() + "/doesNotExist.file"; - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") - .setOutput(nonExistingFilename.getBytes()); - job = jobService.saveJob(job); - - reportJobService.deleteJob(job); - - assertThat(allJobs(), is(empty())); - } - - @Test - public void test_deleteJob_jobOutputIsNull() { - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("").setOutput(null); - job = jobService.saveJob(job); - - reportJobService.deleteJob(job); - - assertThat(allJobs(), is(empty())); - } - - @Test - public void test_getOutputFileOfJob_noJobInDB() { - Job job = new Job().setJobId(-1); - - assertNull(reportJobService.getOutputFileOfJob(job)); - } - - @Test - public void test_getOutputFileOfJob_jobOutputIsNull() { - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("").setOutput(null); - job = jobService.saveJob(job); - - assertNull(reportJobService.getOutputFileOfJob(job)); - } - - @Test - public void test_getOutputFileOfJob_fileDoesNotExist(@TempDir File tempDir) { - String nonExistingFilename = tempDir.getAbsolutePath() + "/doesNotExist.file"; - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") - .setOutput(nonExistingFilename.getBytes()); - job = jobService.saveJob(job); - - assertNull(reportJobService.getOutputFileOfJob(job)); - } - - @Test - public void test_getOutputFileOfJob_fileExists() throws IOException { - File tempFile = File.createTempFile("ReportJobServiceTest", "test2"); - tempFile.deleteOnExit(); - Job job = new Job().setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser).setQueue("") - .setOutput(tempFile.getAbsolutePath().getBytes()); - job = jobService.saveJob(job); - - File f = reportJobService.getOutputFileOfJob(job); - - assertNotNull(f); - assertEquals(tempFile, f); - } - - @Test - public void test_cleanUpOldJobsAndReportFiles() throws IOException { - Instant now = Instant.now(); - Date beforeMaxAge = Date.from(now.minus(MAX_AGE).minusSeconds(1000)); - Date afterMaxAge = Date.from(now.minus(MAX_AGE).plusSeconds(1000)); - - // create jobs and their associated output files - File fileToBeDeleted = File.createTempFile("ReportJobServiceTest", "test"); - File fileToBeKept = File.createTempFile("ReportJobServiceTest", "test"); - fileToBeDeleted.deleteOnExit(); - fileToBeKept.deleteOnExit(); - assertTrue(fileToBeDeleted.exists()); - assertTrue(fileToBeKept.exists()); - - Job jobToBeDeleted = new Job().setJobDate(beforeMaxAge).setJobType(REPORT).setStatus(COMPLETED) - .setOwner(adminUser).setQueue("").setOutput(fileToBeDeleted.getAbsolutePath().getBytes()); - Job jobToBeKept = new Job().setJobDate(afterMaxAge).setJobType(REPORT).setStatus(COMPLETED).setOwner(adminUser) - .setQueue("").setOutput(fileToBeKept.getAbsolutePath().getBytes()); - jobToBeDeleted = jobService.saveJob(jobToBeDeleted); - jobToBeKept = jobService.saveJob(jobToBeKept); - - // create orphaned files in the reports directory - File orphanedFileToBeDeleted = createTempFileInReportsDirectory(); - File orphanedFileToBeKept = createTempFileInReportsDirectory(); - assertTrue(orphanedFileToBeDeleted.exists()); - assertTrue(orphanedFileToBeKept.exists()); - orphanedFileToBeDeleted.setLastModified(beforeMaxAge.getTime()); - orphanedFileToBeKept.setLastModified(afterMaxAge.getTime()); - - // execution - reportJobService.cleanUpOldJobsAndReportFiles(); - - // check jobs and their associated output files - List allJobs = allJobs(); - assertThat(allJobs, hasSize(1)); - assertEquals(jobToBeKept.getJobId(), allJobs.get(0).getJobId()); - - assertFalse(fileToBeDeleted.exists()); - assertTrue(fileToBeKept.exists()); - - // check orphaned files in the reports directory - assertFalse(orphanedFileToBeDeleted.exists()); - assertTrue(orphanedFileToBeKept.exists()); - } - - private File createTempFileInReportsDirectory() throws IOException { - File reportsDir = new File(reportsDirectory.getReportsDirectory()); - File tempFile = File.createTempFile("ReportJobServiceTest", "test", reportsDir); - tempFile.deleteOnExit(); - return tempFile; - } - - private List allJobs() { - Map cmap = new HashMap<>(); - cmap.put(CONDITION_JOBTYPE, REPORT); - return jobService.loadJobs(cmap); - } - - private List pendingJobs() { - Map cmap = new HashMap<>(); - cmap.put(CONDITION_JOBTYPE, REPORT); - cmap.put(CONDITION_STATUS, PENDING); - return jobService.loadJobs(cmap); - } - - private List busyJobs() { - Map cmap = new HashMap<>(); - cmap.put(CONDITION_JOBTYPE, REPORT); - cmap.put(CONDITION_STATUS, BUSY); - return jobService.loadJobs(cmap); - } - - private ReportJobPojo deserializeToReportJobPojo(byte[] bytes) { - Object o = null; - try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(bais)) { - o = ois.readObject(); - } catch (ClassNotFoundException | IOException e) { - throw new RuntimeException(e); - } - return (ReportJobPojo) o; - } -} diff --git a/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java b/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java index c619b4d5b..8ec51d167 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java +++ b/ui/src/test/java/de/ipb_halle/lbac/reporting/mocks/ReportsDirectoryMock.java @@ -17,7 +17,7 @@ */ package de.ipb_halle.lbac.reporting.mocks; -import de.ipb_halle.reporting.report.ReportsDirectory; +import de.ipb_halle.reporting.ReportsDirectory; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -37,7 +37,7 @@ * @author fbroda, flange */ @Singleton(name="reportsDirectory") -@Startup +//@Startup public class ReportsDirectoryMock extends ReportsDirectory { private static final long serialVersionUID = 1L; diff --git a/ui/src/test/java/de/ipb_halle/lbac/util/pref/PreferenceServiceTest.java b/ui/src/test/java/de/ipb_halle/lbac/util/pref/PreferenceServiceTest.java index 7cf2a3845..be5c77917 100644 --- a/ui/src/test/java/de/ipb_halle/lbac/util/pref/PreferenceServiceTest.java +++ b/ui/src/test/java/de/ipb_halle/lbac/util/pref/PreferenceServiceTest.java @@ -17,10 +17,10 @@ */ package de.ipb_halle.lbac.util.pref; -import de.ipb_halle.lbac.EntityManagerService; import de.ipb_halle.lbac.base.TestBase; import de.ipb_halle.lbac.admission.User; import de.ipb_halle.lbac.admission.UserEntity; +import de.ipb_halle.test.EntityManagerService; import de.ipb_halle.testcontainers.PostgresqlContainerExtension; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; diff --git a/ui/src/test/resources/test-persistence.xml b/ui/src/test/resources/test-persistence.xml index aac18637c..bc7edb3ee 100644 --- a/ui/src/test/resources/test-persistence.xml +++ b/ui/src/test/resources/test-persistence.xml @@ -31,69 +31,68 @@ org.hibernate.jpa.HibernatePersistenceProvider - de.ipb_halle.lbac.exp.ExpRecordEntity - de.ipb_halle.lbac.exp.ExperimentEntity + de.ipb_halle.job.JobEntity + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity + de.ipb_halle.lbac.admission.ACEntryEntity + de.ipb_halle.lbac.admission.ACListEntity + de.ipb_halle.lbac.admission.GroupEntity + de.ipb_halle.lbac.admission.MemberEntity + de.ipb_halle.lbac.admission.MembershipEntity + de.ipb_halle.lbac.admission.NestingPathEntity + de.ipb_halle.lbac.admission.NestingPathSetEntity + de.ipb_halle.lbac.admission.UserEntity + de.ipb_halle.lbac.collections.CollectionEntity + de.ipb_halle.lbac.container.entity.ContainerEntity + de.ipb_halle.lbac.container.entity.ContainerNestingEntity + de.ipb_halle.lbac.container.entity.ContainerTypeEntity de.ipb_halle.lbac.datalink.LinkedDataEntity + de.ipb_halle.lbac.device.print.LabelEntity + de.ipb_halle.lbac.device.print.PrinterEntity + de.ipb_halle.lbac.entity.CloudEntity + de.ipb_halle.lbac.entity.CloudNodeEntity + de.ipb_halle.lbac.entity.InfoObjectEntity + de.ipb_halle.lbac.entity.NodeEntity + de.ipb_halle.lbac.exp.ExperimentEntity + de.ipb_halle.lbac.exp.ExpRecordEntity de.ipb_halle.lbac.exp.assay.AssayEntity - de.ipb_halle.lbac.exp.text.TextEntity de.ipb_halle.lbac.exp.image.ImageEntity - - de.ipb_halle.lbac.project.ProjectEntity - de.ipb_halle.lbac.project.ProjectTemplateEntity + de.ipb_halle.lbac.exp.text.TextEntity + de.ipb_halle.lbac.forum.PostingEntity + de.ipb_halle.lbac.forum.TopicEntity + de.ipb_halle.lbac.items.entity.ItemEntity + de.ipb_halle.lbac.items.entity.ItemHistoryEntity + de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity de.ipb_halle.lbac.material.biomaterial.BioMaterialEntity de.ipb_halle.lbac.material.biomaterial.BioMaterialHistoryEntity de.ipb_halle.lbac.material.biomaterial.TaxonomyEntity de.ipb_halle.lbac.material.biomaterial.TaxonomyHistEntity de.ipb_halle.lbac.material.biomaterial.TaxonomyLevelEntity de.ipb_halle.lbac.material.biomaterial.TissueEntity - de.ipb_halle.lbac.material.composition.MaterialCompositionEntity - de.ipb_halle.lbac.material.composition.CompositionHistoryEntity - de.ipb_halle.lbac.material.common.entity.MaterialEntity de.ipb_halle.lbac.material.common.entity.MaterialDetailRightEntity + de.ipb_halle.lbac.material.common.entity.MaterialEntity de.ipb_halle.lbac.material.common.entity.MaterialHistoryEntity + de.ipb_halle.lbac.material.common.entity.hazard.HazardEntity + de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialHistEntity + de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialsEntity de.ipb_halle.lbac.material.common.entity.index.IndexTypeEntity - de.ipb_halle.lbac.material.common.entity.index.MaterialIndexHistoryEntity de.ipb_halle.lbac.material.common.entity.index.MaterialIndexEntryEntity - de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialsEntity - de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialHistEntity - de.ipb_halle.lbac.material.common.entity.hazard.HazardEntity + de.ipb_halle.lbac.material.common.entity.index.MaterialIndexHistoryEntity + de.ipb_halle.lbac.material.common.entity.storage.StorageClassHistoryEntity de.ipb_halle.lbac.material.common.entity.storage.StorageConditionHistoryEntity de.ipb_halle.lbac.material.common.entity.storage.StorageConditionMaterialEntity - de.ipb_halle.lbac.material.common.entity.storage.StorageClassHistoryEntity de.ipb_halle.lbac.material.common.entity.storage.StorageEntity + de.ipb_halle.lbac.material.composition.CompositionHistoryEntity de.ipb_halle.lbac.material.composition.CompositionEntity + de.ipb_halle.lbac.material.composition.MaterialCompositionEntity de.ipb_halle.lbac.material.sequence.SequenceEntity de.ipb_halle.lbac.material.sequence.history.SequenceHistoryEntity de.ipb_halle.lbac.material.structure.StructureEntity de.ipb_halle.lbac.material.structure.StructureHistEntity - de.ipb_halle.lbac.container.entity.ContainerEntity - de.ipb_halle.lbac.container.entity.ContainerNestingEntity - de.ipb_halle.lbac.container.entity.ContainerTypeEntity - de.ipb_halle.lbac.items.entity.ItemEntity - de.ipb_halle.lbac.items.entity.ItemHistoryEntity - de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity - de.ipb_halle.lbac.device.job.JobEntity - de.ipb_halle.lbac.device.print.LabelEntity - de.ipb_halle.lbac.device.print.PrinterEntity - de.ipb_halle.lbac.admission.ACEntryEntity - de.ipb_halle.lbac.admission.ACListEntity - de.ipb_halle.lbac.entity.CloudEntity - de.ipb_halle.lbac.entity.CloudNodeEntity - de.ipb_halle.lbac.collections.CollectionEntity - de.ipb_halle.lbac.admission.GroupEntity - de.ipb_halle.lbac.entity.InfoObjectEntity - de.ipb_halle.lbac.admission.MemberEntity - de.ipb_halle.lbac.admission.MembershipEntity - de.ipb_halle.lbac.admission.NestingPathEntity - de.ipb_halle.lbac.admission.NestingPathSetEntity - de.ipb_halle.lbac.entity.NodeEntity - de.ipb_halle.lbac.admission.UserEntity - de.ipb_halle.lbac.forum.TopicEntity - de.ipb_halle.lbac.forum.PostingEntity + de.ipb_halle.lbac.project.ProjectEntity + de.ipb_halle.lbac.project.ProjectTemplateEntity de.ipb_halle.lbac.util.pref.PreferenceEntity - de.ipb_halle.kx.file.FileObjectEntity - de.ipb_halle.kx.termvector.TermVectorEntity - de.ipb_halle.reporting.report.ReportEntity + de.ipb_halle.reporting.ReportEntity de.ipb_halle.lbac.search.lang.HorrorEntity diff --git a/ui/web/WEB-INF/persistence.xml b/ui/web/WEB-INF/persistence.xml index 7577194fd..3e9d1ea1e 100644 --- a/ui/web/WEB-INF/persistence.xml +++ b/ui/web/WEB-INF/persistence.xml @@ -28,77 +28,77 @@ org.hibernate.jpa.HibernatePersistenceProvider uiDS - + de.ipb_halle.job.JobEntity + de.ipb_halle.kx.file.FileObjectEntity + de.ipb_halle.kx.termvector.TermVectorEntity + de.ipb_halle.lbac.admission.ACEntryEntity + de.ipb_halle.lbac.admission.ACListEntity + de.ipb_halle.lbac.admission.GroupEntity + de.ipb_halle.lbac.admission.MemberEntity + de.ipb_halle.lbac.admission.MembershipEntity + de.ipb_halle.lbac.admission.NestingPathEntity + de.ipb_halle.lbac.admission.NestingPathSetEntity + de.ipb_halle.lbac.admission.UserEntity + de.ipb_halle.lbac.collections.CollectionEntity + de.ipb_halle.lbac.container.entity.ContainerEntity + de.ipb_halle.lbac.container.entity.ContainerNestingEntity + de.ipb_halle.lbac.container.entity.ContainerTypeEntity de.ipb_halle.lbac.datalink.LinkedDataEntity - de.ipb_halle.lbac.project.ProjectEntity - de.ipb_halle.lbac.project.ProjectTemplateEntity + de.ipb_halle.lbac.device.print.LabelEntity + de.ipb_halle.lbac.device.print.PrinterEntity + de.ipb_halle.lbac.entity.CloudEntity + de.ipb_halle.lbac.entity.CloudNodeEntity + de.ipb_halle.lbac.entity.InfoObjectEntity + de.ipb_halle.lbac.entity.NodeEntity de.ipb_halle.lbac.exp.ExperimentEntity de.ipb_halle.lbac.exp.ExpRecordEntity - de.ipb_halle.lbac.exp.image.ImageEntity de.ipb_halle.lbac.exp.assay.AssayEntity - de.ipb_halle.lbac.exp.assay.SOPEntity + de.ipb_halle.lbac.exp.image.ImageEntity de.ipb_halle.lbac.exp.text.TextEntity + de.ipb_halle.lbac.forum.TopicEntity + de.ipb_halle.lbac.forum.PostingEntity + de.ipb_halle.lbac.items.entity.ItemEntity + de.ipb_halle.lbac.items.entity.ItemHistoryEntity + de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity de.ipb_halle.lbac.material.biomaterial.BioMaterialEntity de.ipb_halle.lbac.material.biomaterial.BioMaterialHistoryEntity de.ipb_halle.lbac.material.biomaterial.TaxonomyEntity de.ipb_halle.lbac.material.biomaterial.TaxonomyHistEntity de.ipb_halle.lbac.material.biomaterial.TaxonomyLevelEntity de.ipb_halle.lbac.material.biomaterial.TissueEntity - de.ipb_halle.lbac.material.composition.MaterialCompositionEntity - de.ipb_halle.lbac.material.composition.CompositionHistoryEntity - de.ipb_halle.lbac.material.common.entity.MaterialEntity de.ipb_halle.lbac.material.common.entity.MaterialDetailRightEntity + de.ipb_halle.lbac.material.common.entity.MaterialEntity de.ipb_halle.lbac.material.common.entity.MaterialHistoryEntity de.ipb_halle.lbac.material.common.entity.index.IndexTypeEntity - de.ipb_halle.lbac.material.common.entity.index.MaterialIndexHistoryEntity de.ipb_halle.lbac.material.common.entity.index.MaterialIndexEntryEntity - de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialsEntity - de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialHistEntity + de.ipb_halle.lbac.material.common.entity.index.MaterialIndexHistoryEntity de.ipb_halle.lbac.material.common.entity.hazard.HazardEntity + de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialHistEntity + de.ipb_halle.lbac.material.common.entity.hazard.HazardsMaterialsEntity + de.ipb_halle.lbac.material.common.entity.storage.StorageClassHistoryEntity de.ipb_halle.lbac.material.common.entity.storage.StorageConditionHistoryEntity de.ipb_halle.lbac.material.common.entity.storage.StorageConditionMaterialEntity - de.ipb_halle.lbac.material.common.entity.storage.StorageClassHistoryEntity de.ipb_halle.lbac.material.common.entity.storage.StorageEntity + de.ipb_halle.lbac.material.composition.CompositionHistoryEntity de.ipb_halle.lbac.material.composition.CompositionEntity + de.ipb_halle.lbac.material.composition.MaterialCompositionEntity + de.ipb_halle.lbac.material.entity.StorageClassHistoryEntity + de.ipb_halle.lbac.material.entity.StorageConditionHistoryEntity + de.ipb_halle.lbac.material.entity.StorageConditionStorageEntity + de.ipb_halle.lbac.material.entity.StorageEntity de.ipb_halle.lbac.material.sequence.SequenceEntity de.ipb_halle.lbac.material.sequence.history.SequenceHistoryEntity de.ipb_halle.lbac.material.structure.StructureEntity de.ipb_halle.lbac.material.structure.StructureHistEntity - de.ipb_halle.lbac.device.job.JobEntity - de.ipb_halle.lbac.device.print.LabelEntity - de.ipb_halle.lbac.device.print.PrinterEntity - de.ipb_halle.lbac.admission.ACEntryEntity - de.ipb_halle.lbac.admission.ACListEntity - de.ipb_halle.lbac.entity.CloudEntity - de.ipb_halle.lbac.entity.CloudNodeEntity - de.ipb_halle.lbac.collections.CollectionEntity - de.ipb_halle.lbac.admission.GroupEntity - de.ipb_halle.lbac.entity.InfoObjectEntity - de.ipb_halle.lbac.admission.MemberEntity - de.ipb_halle.lbac.admission.MembershipEntity - de.ipb_halle.lbac.admission.NestingPathEntity - de.ipb_halle.lbac.admission.NestingPathSetEntity - de.ipb_halle.lbac.entity.NodeEntity - de.ipb_halle.lbac.admission.UserEntity - de.ipb_halle.lbac.forum.TopicEntity - de.ipb_halle.lbac.forum.PostingEntity + de.ipb_halle.lbac.project.ProjectEntity + de.ipb_halle.lbac.project.ProjectTemplateEntity + de.ipb_halle.lbac.util.pref.PreferenceEntity - de.ipb_halle.lbac.reporting.report.ReportEntity - de.ipb_halle.lbac.container.entity.ContainerEntity - de.ipb_halle.lbac.container.entity.ContainerNestingEntity - de.ipb_halle.lbac.container.entity.ContainerTypeEntity - de.ipb_halle.lbac.material.entity.StorageConditionHistoryEntity - de.ipb_halle.lbac.material.entity.StorageConditionStorageEntity - de.ipb_halle.lbac.material.entity.StorageClassHistoryEntity - de.ipb_halle.lbac.material.entity.StorageEntity - de.ipb_halle.lbac.items.entity.ItemEntity - de.ipb_halle.lbac.items.entity.ItemHistoryEntity - de.ipb_halle.lbac.items.entity.ItemPositionsHistoryEntity - de.ipb_halle.kx.file.FileObjectEntity - de.ipb_halle.kx.termvector.TermVectorEntity + de.ipb_halle.reporting.ReportEntity true diff --git a/ui/web/WEB-INF/templates/myReports.xhtml b/ui/web/WEB-INF/templates/myReports.xhtml index 7cc08dee7..bd418da3d 100644 --- a/ui/web/WEB-INF/templates/myReports.xhtml +++ b/ui/web/WEB-INF/templates/myReports.xhtml @@ -43,31 +43,31 @@ + row-style-class="#{job.rowStyleClass}"> - + @@ -75,8 +75,8 @@ ajax="true" process="@this" update="reportingJobsTableId" - action="#{reportingJobsBean.actionDeleteReport(jobWrapper)}" - rendered="#{jobWrapper.deleteable}" + action="#{reportingJobsBean.actionDeleteReport(job)}" + rendered="#{job.deleteable}" style="padding: 0 4px;" icon="trash" look="link" /> From 6e6cadc6fe0fc58624708f00468a86f0e0e03c46 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Mon, 13 Nov 2023 09:56:38 +0100 Subject: [PATCH 23/28] fix minor issues in reporting --- .../reporting/ReportSchedulingService.java | 6 +++ .../reporting/ReportingJobService.java | 17 ++++++- reporting/src/main/resources/log4j2.xml | 42 ++++++++++++++++ reporting/src/main/webapp/WEB-INF/beans.xml | 6 +++ .../src/main/webapp/WEB-INF/persistence.xml | 49 +++++++++++++++++++ ui/web/WEB-INF/templates/myReports.xhtml | 4 +- 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 reporting/src/main/resources/log4j2.xml create mode 100644 reporting/src/main/webapp/WEB-INF/beans.xml create mode 100644 reporting/src/main/webapp/WEB-INF/persistence.xml diff --git a/reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java index 55e672b4d..8f2cbde8b 100644 --- a/reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportSchedulingService.java @@ -24,6 +24,9 @@ import javax.ejb.Startup; import javax.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + /** * Timer service for maintaining reporting jobs. * @@ -33,6 +36,8 @@ @Startup public class ReportSchedulingService { + private Logger logger = LogManager.getLogger(ReportSchedulingService.class.getName()); + @Inject private ReportingJobService reportJobService; @@ -42,6 +47,7 @@ public class ReportSchedulingService { */ @PostConstruct public void startUp() { + logger.info("ReportSchedulingService has been started ..."); reportJobService.markBusyJobsAsPending(); // Could cause heavy load on application startup? diff --git a/reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java b/reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java index 0897a6c64..d617b1ae8 100644 --- a/reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java +++ b/reporting/src/main/java/de/ipb_halle/reporting/ReportingJobService.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.RejectedExecutionException; +import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.Stateless; import javax.enterprise.concurrent.ManagedExecutorService; @@ -71,6 +72,14 @@ public class ReportingJobService extends JobService { @Inject private ReportsDirectory reportsDirectory; + @PostConstruct + private void init() { + if (managedExecutorService != null) { + logger.info("ManagedExecutorService has been injected sucessfully into ReportingJobService"); + } else { + logger.info("ManagedExecutorService MISSING in ReportingJobService"); + } + } protected ReportingJob buildJob(JobEntity e) { if (e != null) { @@ -95,11 +104,17 @@ public void markBusyJobsAsPending() { * tasks. */ public void submitPendingJobsToExecutor() { - for (ReportingJob job : pendingJobs()) { + List jobs = pendingJobs(); + int countSubmitted = 0; + for (ReportingJob job : jobs) { boolean submitSuccessful = submitJob(job); if (!submitSuccessful) { break; } + countSubmitted++; + } + if (countSubmitted > 0) { + logger.info("Successfully submitted {} of {} reporting jobs to execution", countSubmitted, jobs.size()); } } diff --git a/reporting/src/main/resources/log4j2.xml b/reporting/src/main/resources/log4j2.xml new file mode 100644 index 000000000..d429c715f --- /dev/null +++ b/reporting/src/main/resources/log4j2.xml @@ -0,0 +1,42 @@ + + + + + /usr/local/tomee/logs/ + + + + + + + + + + + [%-5p] %d{yyyy-MM-dd HH:mm:ss} %c{1} - %m%n + + + + + + + + + + + + + + + + + + + + + + diff --git a/reporting/src/main/webapp/WEB-INF/beans.xml b/reporting/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 000000000..ff45d1767 --- /dev/null +++ b/reporting/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/reporting/src/main/webapp/WEB-INF/persistence.xml b/reporting/src/main/webapp/WEB-INF/persistence.xml new file mode 100644 index 000000000..45c4628e1 --- /dev/null +++ b/reporting/src/main/webapp/WEB-INF/persistence.xml @@ -0,0 +1,49 @@ + + + + + + CRIMSy Reporting + + org.hibernate.jpa.HibernatePersistenceProvider + uiDS + + de.ipb_halle.job.JobEntity + true + + + + + + + + + + + + + + + diff --git a/ui/web/WEB-INF/templates/myReports.xhtml b/ui/web/WEB-INF/templates/myReports.xhtml index bd418da3d..d3e2b7665 100644 --- a/ui/web/WEB-INF/templates/myReports.xhtml +++ b/ui/web/WEB-INF/templates/myReports.xhtml @@ -52,9 +52,9 @@ order="desc"> - + From c4b789f552d8954da43f307a8476df254789d56d Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Mon, 13 Nov 2023 09:58:50 +0100 Subject: [PATCH 24/28] adjust infrastructure code --- docker/ui/Dockerfile | 1 + util/bin/buildDocker.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/docker/ui/Dockerfile b/docker/ui/Dockerfile index f33f7efdc..d545b0470 100644 --- a/docker/ui/Dockerfile +++ b/docker/ui/Dockerfile @@ -28,6 +28,7 @@ COPY setup.sh / COPY extralib /usr/local/tomee/extralib COPY ui.war /usr/local/tomee/webapps/ COPY kx-web.war /usr/local/tomee/webapps/ +COPY reporting.war /usr/local/tomee/webapps/ COPY logpurge.sh /usr/local/bin/ COPY tomcat-users.xml /usr/local/tomee/conf/ COPY tomee.xml /usr/local/tomee/conf/ diff --git a/util/bin/buildDocker.sh b/util/bin/buildDocker.sh index ddea344df..9707e8daf 100755 --- a/util/bin/buildDocker.sh +++ b/util/bin/buildDocker.sh @@ -42,6 +42,7 @@ function compile { cp -r target/extralib target/docker/ui/ cp ui/target/ui.war target/docker/ui/ cp kx-web/target/kx-web.war target/docker/ui/ + cp reporting/target/reporting.war target/docker/ui/ if [ -n "$STAGE_LABEL" ] ; then flags="$flags,$STAGE_LABEL" From 6ca88b5af03418130f64152633c2677a385f33bb Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Tue, 14 Nov 2023 15:26:47 +0100 Subject: [PATCH 25/28] fixes from integration test --- .../test/java/de/ipb_halle/kx/service/FileAnalyserTest.java | 4 +++- util/bin/testSetup.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java index 9da196eec..da948f50a 100644 --- a/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java +++ b/kx-web/src/test/java/de/ipb_halle/kx/service/FileAnalyserTest.java @@ -134,7 +134,9 @@ public void test004_analyseStemming() throws FileNotFoundException { @Test public void test005_checkUniqueWordOrigins() throws FileNotFoundException, Exception { FileAnalyser analyser = setupAnalyser("IPB_Jahresbericht_2004.pdf"); - Assert.assertEquals(5319, analyser.getTermVector().size()); +// Assert.assertEquals(5319, analyser.getTermVector().size()); + int count = analyser.getTermVector().size(); + Assert.assertTrue((5315 < count) && (count < 5330)); Assert.assertEquals("de", analyser.getLanguage()); } diff --git a/util/bin/testSetup.sh b/util/bin/testSetup.sh index 6be5543ca..86bcbe299 100755 --- a/util/bin/testSetup.sh +++ b/util/bin/testSetup.sh @@ -855,7 +855,7 @@ UPDATE='' UPDATE_CMD='container' WAKE='' -GETOPT=$(getopt -o 'b:H:hj:n:p:R:rS:stu:w:' --longoptions 'branch-file:hostlist:,help,jobs:,node:,pause:,restore:,runTests,snapshot:,setup,teardown,update:wake:' -n 'testSetup.sh' -- "$@") +GETOPT=$(getopt -o 'b:H:hj:n:p:R:rS:stu:w:' --longoptions 'branch-file:,hostlist:,help,jobs:,node:,pause:,restore:,runTests,snapshot:,setup,teardown,update:wake:' -n 'testSetup.sh' -- "$@") if [ $? -ne 0 ]; then echo 'Error in commandline evaluation. Terminating...' >&2 From 8d9610012e006db8b9f8bb6b892af6f92215c3b7 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Tue, 14 Nov 2023 15:47:50 +0100 Subject: [PATCH 26/28] fixes from integration test --- pom.xml | 1 + util/bin/buildDocker.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fe150ba71..b0881e364 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ kx-api kx-web reporting-api + reporting ui diff --git a/util/bin/buildDocker.sh b/util/bin/buildDocker.sh index 9707e8daf..a8052c9fc 100755 --- a/util/bin/buildDocker.sh +++ b/util/bin/buildDocker.sh @@ -32,7 +32,7 @@ function compile { mvn --batch-mode -DskipTests clean install pushd ui - REVISION=`mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout` + REVISION=`mvn org.apache.maven.plugins:maven-help-plugin:evaluate --batch-mode -Dexpression=project.version -q -DforceStdout` MAJOR=`echo $REVISION | cut -d. -f1` MINOR=`echo $REVISION | cut -d. -f2` popd From 3bb6e4753e7c1d9493e2d67772a1901683d36432 Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Wed, 15 Nov 2023 09:08:27 +0100 Subject: [PATCH 27/28] project version, version handling --- ui/pom.xml | 2 +- util/bin/buildDocker.sh | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/pom.xml b/ui/pom.xml index 82a0c8cdd..e679cc006 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -25,7 +25,7 @@ de.ipb-halle ui - 2.1.5 + 2.1.6 scm:git:https://github.com/ipb-halle/CRIMSy.git https://github.com/ipb-halle/CRIMSy.git diff --git a/util/bin/buildDocker.sh b/util/bin/buildDocker.sh index a8052c9fc..eb27e233a 100755 --- a/util/bin/buildDocker.sh +++ b/util/bin/buildDocker.sh @@ -32,7 +32,9 @@ function compile { mvn --batch-mode -DskipTests clean install pushd ui - REVISION=`mvn org.apache.maven.plugins:maven-help-plugin:evaluate --batch-mode -Dexpression=project.version -q -DforceStdout` + # maven 3.8.7 does output ANSI escape sequences, even in batch mode + REVISION=`mvn org.apache.maven.plugins:maven-help-plugin:evaluate --batch-mode -Dexpression=project.version -q -DforceStdout | \ + sed -Ee 's/(\x1b...)?([1-9]\.[0-9]+\.[[:alnum:]_\-]+)(\x1b...)?/\2/'` MAJOR=`echo $REVISION | cut -d. -f1` MINOR=`echo $REVISION | cut -d. -f2` popd From bf5ebe368c8cf4fb11a72988c654295fdc264e2b Mon Sep 17 00:00:00 2001 From: Frank Broda Date: Wed, 15 Nov 2023 10:10:49 +0100 Subject: [PATCH 28/28] cleanup poms --- agency/util/bin/testSetup.sh | 6 ++++-- kx-web/pom.xml | 32 -------------------------------- reporting/pom.xml | 24 +----------------------- ui/pom.xml | 11 +++-------- 4 files changed, 8 insertions(+), 65 deletions(-) diff --git a/agency/util/bin/testSetup.sh b/agency/util/bin/testSetup.sh index 79e943d67..93de8f1ac 100755 --- a/agency/util/bin/testSetup.sh +++ b/agency/util/bin/testSetup.sh @@ -20,7 +20,8 @@ java -Djavax.net.ssl.trustStore=./truststore \\ -jar ../target/crimsy-agency-1.0.jar \\ -p ./agency_secret.txt \\ -s ./agent.sh \\ - -u https://biocloud.somewhere.invalid/ui/rest/jobs + -t PRINT \\ + -u https://biocloud.somewhere.invalid/ui/rest/print EOF cat < test/agent.sh @@ -40,9 +41,10 @@ cat < - - - - org.hibernate @@ -249,14 +239,6 @@ 3.0.1-b12 test - @@ -326,21 +308,7 @@ 7.0.9 test - org.awaitility awaitility diff --git a/reporting/pom.xml b/reporting/pom.xml index 8b3e81c90..18411213c 100644 --- a/reporting/pom.xml +++ b/reporting/pom.xml @@ -110,7 +110,7 @@ true 8 - {0} KX (git-sha1:{2} * {1,date,yyyy-MM-dd HH:mm:ss}) + {0} reporting (git-sha1:{2} * {1,date,yyyy-MM-dd HH:mm:ss}) ${project.version} timestamp @@ -288,14 +288,6 @@ 3.0.1-b12 test - @@ -364,21 +356,7 @@ 7.0.9 test - org.awaitility awaitility diff --git a/ui/pom.xml b/ui/pom.xml index e679cc006..afa10a836 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -624,12 +624,13 @@ + commons-io @@ -644,13 +645,7 @@ httpclient 4.5.14 - + de.ipb-halle