From 0ddbd63ccc5ade8f8d06af615d4fee4234dc92e5 Mon Sep 17 00:00:00 2001
From: Daniel Rainer <daniel.m.rainer@student.uibk.ac.at>
Date: Sat, 6 Mar 2021 18:52:39 +0100
Subject: [PATCH] Split user provided and auto-generated metadata

ElasticSearch returns search results as JSON
files which have three top level entries
for us to parse:
"project", "file", and "metadata".
Change the class structure to match this
hierarchy. This means, that the mapping
from JSON to Java objects just works
and we can pass the data on to the
front-end in the same structure as
the back-end received it.

This allows the separation of the
"standardized" metadata which is provided
by the platform's users
and our implementation-detail metadata,
such as GitLab related data.
This way, no conflicts arise when a new
key is introduced to the metadata standard.
---
 .../repository/search/MetaDataRepository.java |  36 +-
 .../uibk/gitsearch/service/SearchService.java |  53 +-
 .../service/dto/SearchResultDTO.java          | 467 +++++------------
 .../service/dto/SearchResultsDTO.java         | 173 -------
 .../service/dto/UserProvidedMetadataDTO.java  | 471 ++++++++++++++++++
 5 files changed, 615 insertions(+), 585 deletions(-)
 create mode 100644 src/main/java/at/ac/uibk/gitsearch/service/dto/UserProvidedMetadataDTO.java

diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
index 85848ab7f..1cdf18e3d 100644
--- a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
+++ b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
@@ -35,39 +35,12 @@ import at.ac.uibk.gitsearch.config.ApplicationProperties;
 import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
 import at.ac.uibk.gitsearch.service.dto.SearchInputDTO;
 import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO;
-import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO.GitProject;
-import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO.Person;
+import at.ac.uibk.gitsearch.service.dto.UserProvidedMetadataDTO.Person;
 import at.ac.uibk.gitsearch.service.dto.SearchResultDTO;
 
 @Repository
 public class MetaDataRepository {
 
-	/**
-	 * just a local wrapper, to decrypt JSON
-	 *
-	 */
-	public static class SearchResultDTOWrapper {
-        private GitProject project;
-		private SearchResultDTO metadata = new SearchResultDTO();
-
-        public GitProject getProject() {
-            return project;
-        }
-
-        public void setProject(GitProject project) {
-            this.project = project;
-        }
-
-		public SearchResultDTO getMetadata() {
-			return metadata;
-		}
-
-		public void setMetadata(SearchResultDTO metadata) {
-			this.metadata = metadata;
-		}
-
-	}
-
 	private final RestHighLevelClient elasticsearchClient;
 
 	private final ApplicationProperties properties;
@@ -198,7 +171,7 @@ public class MetaDataRepository {
 
 				for (SearchHit searchHit : searchResponse.getHits().getHits()) {
 					final String sourceAsJSON = searchHit.getSourceAsString();
-					SearchResultDTOWrapper entry = objectMapper.readValue(sourceAsJSON, SearchResultDTOWrapper.class);
+					SearchResultDTO entry = objectMapper.readValue(sourceAsJSON, SearchResultDTO.class);
 					if (entry.getMetadata().getKeyword() != null)
 						for (String keyWord : entry.getMetadata().getKeyword())
 							addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_KEYWORDS),
@@ -321,10 +294,7 @@ public class MetaDataRepository {
     }
 
     private static SearchResultDTO parseSearchHit(SearchHit searchHit, ObjectMapper objectMapper) throws JsonProcessingException {
-        SearchResultDTOWrapper entry = objectMapper.readValue(searchHit.getSourceAsString(),
-            SearchResultDTOWrapper.class);
-        final SearchResultDTO metadata = entry.getMetadata();
-        return metadata;
+        return objectMapper.readValue(searchHit.getSourceAsString(), SearchResultDTO.class);
     }
 
 	/** Helper **/
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
index dbac22517..1b01871e0 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
@@ -7,7 +7,6 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
@@ -136,24 +135,24 @@ public class SearchService {
 	private boolean testAccess(SearchResultDTO p, GitLabApi gitLabApi) {
 		if(true) return true;
 		if(gitLabApi==null) return false;
-					try {
-						Project gitProject = gitLabApi.getProjectApi().getProject(p.getProject().getProject_id());
-						final Permissions permissions = gitProject.getPermissions();
-						log.info("Permissions {}", permissions);
-						boolean access =
-								permissions.getProjectAccess()!=null  &&
-								permissions.getProjectAccess().getAccessLevel().toValue() >= AccessLevel.GUEST.toValue() ||
-								permissions.getGroupAccess() !=null &&
-								permissions.getGroupAccess().getAccessLevel().toValue() >= AccessLevel.GUEST.toValue() ;
-						return access;
-					} catch (GitLabApiException e) {
-						log.debug("It seems that current user cannot access project {}({}) for {}", p.getTitle(), p.getIdentifier(), p.getProject().getProject_id());
-						return false;
-					}
+        try {
+            Project gitProject = gitLabApi.getProjectApi().getProject(p.getProject().getProject_id());
+            final Permissions permissions = gitProject.getPermissions();
+            log.info("Permissions {}", permissions);
+            boolean access =
+                    permissions.getProjectAccess()!=null  &&
+                    permissions.getProjectAccess().getAccessLevel().toValue() >= AccessLevel.GUEST.toValue() ||
+                    permissions.getGroupAccess() !=null &&
+                    permissions.getGroupAccess().getAccessLevel().toValue() >= AccessLevel.GUEST.toValue() ;
+            return access;
+        } catch (GitLabApiException e) {
+            log.debug("It seems that current user cannot access project {}({}) for {}", p.getMetadata().getTitle(), p.getMetadata().getIdentifier(), p.getProject().getProject_id());
+            return false;
+        }
 	}
 
 	private void fixImageURL(SearchResultDTO metaData, GitLabApi gitLabApi) {
-		String image = metaData.getImage();
+		String image = metaData.getMetadata().getImage();
 
 		URI url = null;
 		try {
@@ -162,23 +161,23 @@ public class SearchService {
 
 		final String baseUrl =
 				ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
-		final boolean isJava = metaData.getProgrammingLanguage() != null && Arrays.stream(metaData.getProgrammingLanguage()).anyMatch(s -> s.startsWith("JAVA"))
+		final boolean isJava = metaData.getMetadata().getProgrammingLanguage() != null && Arrays.stream(metaData.getMetadata().getProgrammingLanguage()).anyMatch(s -> s.startsWith("JAVA"))
 				&& metaData.hashCode() % 2 == 0; // nur um ein wenig Abwechslung zu bekommen :-)
-		final boolean isLatexFormat = metaData.getFormat()!=null && Arrays.stream(metaData.getFormat()).anyMatch(s -> s.startsWith("latex"));
-		final boolean isPython = metaData.getProgrammingLanguage() != null && Arrays.stream(metaData.getProgrammingLanguage()).anyMatch(s -> s.startsWith("python"));
+		final boolean isLatexFormat = metaData.getMetadata().getFormat()!=null && Arrays.stream(metaData.getMetadata().getFormat()).anyMatch(s -> s.startsWith("latex"));
+		final boolean isPython = metaData.getMetadata().getProgrammingLanguage() != null && Arrays.stream(metaData.getMetadata().getProgrammingLanguage()).anyMatch(s -> s.startsWith("python"));
 		if(isJava) {
-			metaData.setImage(baseUrl + "/content/img/java.png");
+			metaData.getMetadata().setImage(baseUrl + "/content/img/java.png");
 			return;
 		}
 		if(isLatexFormat) {
-			metaData.setImage(baseUrl + "/content/img/latex.png");
+			metaData.getMetadata().setImage(baseUrl + "/content/img/latex.png");
 			return;
 		}
 		if(isPython) {
-			metaData.setImage(baseUrl + "/content/img/python.png");
+			metaData.getMetadata().setImage(baseUrl + "/content/img/python.png");
 			return;
 		}
-		metaData.setImage(baseUrl + "/content/img/gitLab.png");
+		metaData.getMetadata().setImage(baseUrl + "/content/img/gitLab.png");
 		if(true) return;
 
 		if(gitLabApi==null) return;
@@ -188,13 +187,13 @@ public class SearchService {
 			final String httpUrlToRepo = gitProject.getHttpUrlToRepo();
 			String baseRepoURL = httpUrlToRepo.replaceFirst(".git", "/-/raw/master/");
 			final URI resolvedImageUrl = new URI(baseRepoURL).resolve(url);
-			metaData.setImage(resolvedImageUrl.toASCIIString());
+			metaData.getMetadata().setImage(resolvedImageUrl.toASCIIString());
 		} catch (GitLabApiException e) {
-			log.debug("It seems that current user cannot access project {}({}) for {}", metaData.getTitle(), metaData.getIdentifier(), metaData.getProject().getProject_id());
+			log.debug("It seems that current user cannot access project {}({}) for {}", metaData.getMetadata().getTitle(), metaData.getMetadata().getIdentifier(), metaData.getProject().getProject_id());
 		}
 
 		} catch (URISyntaxException e) {
-			log.warn("Cannot parse image url {} for {}", metaData.getImage(), metaData.getGitURL());
+			log.warn("Cannot parse image url {} for {}", metaData.getMetadata().getImage(), metaData.getProject().getUrl());
 		}
 	}
 
@@ -234,7 +233,7 @@ public class SearchService {
                 while (metadataPos < length && metadataPos < loadedMetaData.size()) {
                     final SearchResultDTO loadedMetaDataRecord = new SearchResultDTO(loadedMetaData.get(metadataPos++));
                     if (loadedMetaDataRecord.toString().contains(searchString)) {
-                        loadedMetaDataRecord.setTitle("#" + (recordNr + 1) + " " + loadedMetaDataRecord.getTitle());
+                        loadedMetaDataRecord.getMetadata().setTitle("#" + (recordNr + 1) + " " + loadedMetaDataRecord.getMetadata().getTitle());
                         result.add(loadedMetaDataRecord);
                         recordNr++;
                         log.debug("Added MetaData Record {}", loadedMetaData);
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultDTO.java b/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultDTO.java
index 85383d4ab..e681ea93e 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultDTO.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultDTO.java
@@ -1,398 +1,161 @@
 package at.ac.uibk.gitsearch.service.dto;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
-import java.util.Arrays;
 import java.util.Objects;
 
 public class SearchResultDTO {
 
+    private GitProject project;
+    private UserProvidedMetadataDTO metadata;
+
     public SearchResultDTO() {
         // default constructor
     }
 
-    private String metadataVersion; // just for YAML test data reader
-    private String title;
-    private String identifier;
-    private String version;
-    private String structure;
-
-    private String description;
-    private SearchResultsDTO.ExerciseType type;
-    private String license;
-    private String[] keyword;
-
-    private String[] format;
-    private String[] programmingLanguage;
-    private String[] language;
-    private String status;
-
-    private String educationLevel;
-
-    private String gitURL;
-
-    @JsonIgnore
-    private String audience;
-
-    private String timeRequired;
-
-    private SearchResultsDTO.GitProject project;
-
-    @JsonIgnore
-    private String[] collectionContent;
-
-    public String getTimeRequired() {
-        return timeRequired;
-    }
-
-    public void setTimeRequired(String timeRequired) {
-        this.timeRequired = timeRequired;
-    }
-
-    private SearchResultsDTO.Person[] creator;
-    private SearchResultsDTO.Person[] publisher;
-    private SearchResultsDTO.Person[] contributor;
-
-    private boolean deprecated;
-
-    private String difficulty;
-    private String[] source;
-
-    private String[] requires;
-    private String image;
-
-    public String getIdentifier() {
-        return identifier;
-    }
-
-    public void setIdentifier(String identifier) {
-        this.identifier = identifier;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    public void setVersion(String version) {
-        this.version = version;
-    }
-
-    public SearchResultsDTO.ExerciseType getType() {
-        return type;
-    }
-
-    public void setType(SearchResultsDTO.ExerciseType type) {
-        this.type = type;
-    }
-
-    public String[] getLanguage() {
-        return language;
-    }
-
-    public void setLanguage(String[] language) {
-        this.language = language;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public String getEducationLevel() {
-        return educationLevel;
-    }
-
-    public void setEducationLevel(String educationLevel) {
-        this.educationLevel = educationLevel;
-    }
-
-    public String getAudience() {
-        return audience;
-    }
-
-    public void setAudience(String audience) {
-        this.audience = audience;
-    }
-
-    public SearchResultsDTO.Person[] getCreator() {
-        return creator;
-    }
-
-    public void setCreator(SearchResultsDTO.Person[] creator) {
-        this.creator = creator;
-    }
-
-    public SearchResultsDTO.Person[] getPublisher() {
-        return publisher;
-    }
-
-    public void setPublisher(SearchResultsDTO.Person[] publisher) {
-        this.publisher = publisher;
-    }
-
-    public SearchResultsDTO.Person[] getContributor() {
-        return contributor;
-    }
-
-    public void setContributor(SearchResultsDTO.Person[] contributor) {
-        this.contributor = contributor;
-    }
-
-    public boolean isDeprecated() {
-        return deprecated;
-    }
-
-    public void setDeprecated(boolean deprecated) {
-        this.deprecated = deprecated;
-    }
-
-    public String getDifficulty() {
-        return difficulty;
-    }
-
-    public void setDifficulty(String difficulty) {
-        this.difficulty = difficulty;
-    }
-
-    public String[] getSource() {
-        return source;
-    }
-
-    public void setSource(String[] source) {
-        this.source = source;
-    }
-
-    public String[] getRequires() {
-        return requires;
-    }
-
-    public void setRequires(String[] requires) {
-        this.requires = requires;
-    }
-
-    public String getImage() {
-        return image;
-    }
-
-    public void setImage(String image) {
-        this.image = image;
-    }
-
-    private String repositoryURL;
-
-    public String getMetadataVersion() {
-        return metadataVersion;
-    }
-
-    public void setMetadataVersion(String metadataVersion) {
-        this.metadataVersion = metadataVersion;
+    /**
+     * clone constructor
+     *
+     * @param toClone
+     */
+    public SearchResultDTO(SearchResultDTO toClone) {
+        super();
+        this.project = toClone.project;
+        this.metadata = toClone.metadata;
     }
 
-    public String getTitle() {
-        return title;
+    public GitProject getProject() {
+        return project;
     }
 
-    public void setTitle(String title) {
-        this.title = title;
+    public void setProject(GitProject project) {
+        this.project = project;
     }
 
-    public String getDescription() {
-        return description;
+    public UserProvidedMetadataDTO getMetadata() {
+        return metadata;
     }
 
-    public void setDescription(String description) {
-        this.description = description;
+    public void setMetadata(UserProvidedMetadataDTO metadata) {
+        this.metadata = metadata;
     }
 
-    public String getLicense() {
-        return license;
+    @Override
+    public String toString() {
+        return "SearchResultDTO : { project: " + this.project
+            + ", metadata: " + this.metadata
+            + " }";
     }
 
-    public void setLicense(String license) {
-        this.license = license;
+    @Override
+    public int hashCode() {
+        int prime = 31;
+        int result = this.project.hashCode();
+        result = prime * result + this.metadata.hashCode();
+        return result;
     }
 
-    public String[] getKeyword() {
-        return keyword;
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof SearchResultDTO)) {
+            return false;
+        }
+        SearchResultDTO other = (SearchResultDTO) obj;
+        return Objects.equals(this.project, other.project)
+            && Objects.equals(this.metadata, other.metadata);
     }
 
-    public void setKeyword(String[] keyword) {
-        this.keyword = keyword;
-    }
+    public static class GitProject {
+        private int project_id;
+        private String project_name;
+        private String namespace;
+        private String main_group;
+        private String sub_group;
+        private String url;
 
-    public String[] getFormat() {
-        return format;
-    }
+        public int getProject_id() {
+            return project_id;
+        }
 
-    public void setFormat(String[] format) {
-        this.format = format;
-    }
+        public void setProject_id(int project_id) {
+            this.project_id = project_id;
+        }
 
-    public String[] getProgrammingLanguage() {
-        return programmingLanguage;
-    }
+        public String getProject_name() {
+            return project_name;
+        }
 
-    public void setProgrammingLanguage(String[] programmingLanguage) {
-        this.programmingLanguage = programmingLanguage;
-    }
+        public void setProject_name(String project_name) {
+            this.project_name = project_name;
+        }
 
-    public String getRepositoryURL() {
-        return repositoryURL;
-    }
+        public String getNamespace() {
+            return namespace;
+        }
 
-    public void setRepositoryURL(String repositoryURL) {
-        this.repositoryURL = repositoryURL;
-    }
+        public void setNamespace(String namespace) {
+            this.namespace = namespace;
+        }
 
-    public String getStructure() {
-        return structure;
-    }
+        public String getMain_group() {
+            return main_group;
+        }
 
-    public void setStructure(String structure) {
-        this.structure = structure;
-    }
+        public void setMain_group(String main_group) {
+            this.main_group = main_group;
+        }
 
-    public String getGitURL() {
-        return gitURL;
-    }
+        public String getSub_group() {
+            return sub_group;
+        }
 
-    public void setGitURL(String gitURL) {
-        this.gitURL = gitURL;
-    }
+        public void setSub_group(String sub_group) {
+            this.sub_group = sub_group;
+        }
 
-    public SearchResultsDTO.GitProject getProject() {
-        return project;
-    }
+        public String getUrl() {
+            return url;
+        }
 
-    public void setProject(SearchResultsDTO.GitProject project) {
-        this.project = project;
-    }
+        public void setUrl(String url) {
+            this.url = url;
+        }
 
-    /**
-     * clone constructor
-     *
-     * @param toClone
-     */
-    public SearchResultDTO(SearchResultDTO toClone) {
-        super();
-        this.metadataVersion = toClone.metadataVersion;
-        this.title = toClone.title;
-        this.identifier = toClone.identifier;
-        this.version = toClone.version;
-        this.structure = toClone.structure;
-        this.description = toClone.description;
-        this.type = toClone.type;
-        this.license = toClone.license;
-        this.keyword = toClone.keyword;
-        this.format = toClone.format;
-        this.programmingLanguage = toClone.programmingLanguage;
-        this.language = toClone.language;
-        this.status = toClone.status;
-        this.educationLevel = toClone.educationLevel;
-        this.gitURL = toClone.gitURL;
-        this.audience = toClone.audience;
-        this.timeRequired = toClone.timeRequired;
-        this.collectionContent = toClone.collectionContent;
-        this.creator = toClone.creator;
-        this.publisher = toClone.publisher;
-        this.contributor = toClone.contributor;
-        this.deprecated = toClone.deprecated;
-        this.difficulty = toClone.difficulty;
-        this.source = toClone.source;
-        this.requires = toClone.requires;
-        this.image = toClone.image;
-        this.repositoryURL = toClone.repositoryURL;
-    }
+        public int getProjectId() {
+            return project_id;
+        }
 
-    @Override
-    public String toString() {
-        return "SearchResultDTO [audience=" + audience + ", collectionContent=" + Arrays.toString(collectionContent)
-            + ", contributor=" + Arrays.toString(contributor) + ", creator=" + Arrays.toString(creator)
-            + ", deprecated=" + deprecated + ", description=" + description + ", difficulty=" + difficulty
-            + ", educationLevel=" + educationLevel + ", format=" + Arrays.toString(format) + ", gitURL=" + gitURL
-            + ", identifier=" + identifier + ", image=" + image + ", keyword=" + Arrays.toString(keyword)
-            + ", language=" + Arrays.toString(language) + ", license=" + license + ", metadataVersion="
-            + metadataVersion + ", programmingLanguage=" + Arrays.toString(programmingLanguage) + ", publisher="
-            + Arrays.toString(publisher) + ", repositoryURL=" + repositoryURL + ", requires=" + Arrays.toString(requires)
-            + ", source=" + Arrays.toString(source) + ", status=" + status + ", structure=" + structure
-            + ", timeRequired=" + timeRequired + ", title=" + title + ", type=" + type + ", version=" + version
-            + "]";
-    }
+        public void setProjectId(int project_id) {
+            this.project_id = project_id;
+        }
 
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((audience == null) ? 0 : audience.hashCode());
-        result = prime * result + Arrays.hashCode(collectionContent);
-        result = prime * result + Arrays.hashCode(contributor);
-        result = prime * result + Arrays.hashCode(creator);
-        result = prime * result + (deprecated ? 1231 : 1237);
-        result = prime * result + ((description == null) ? 0 : description.hashCode());
-        result = prime * result + ((difficulty == null) ? 0 : difficulty.hashCode());
-        result = prime * result + ((educationLevel == null) ? 0 : educationLevel.hashCode());
-        result = prime * result + Arrays.hashCode(format);
-        result = prime * result + ((gitURL == null) ? 0 : gitURL.hashCode());
-        result = prime * result + ((identifier == null) ? 0 : identifier.hashCode());
-        result = prime * result + ((image == null) ? 0 : image.hashCode());
-        result = prime * result + Arrays.hashCode(keyword);
-        result = prime * result + Arrays.hashCode(language);
-        result = prime * result + ((license == null) ? 0 : license.hashCode());
-        result = prime * result + ((metadataVersion == null) ? 0 : metadataVersion.hashCode());
-        result = prime * result + Arrays.hashCode(programmingLanguage);
-        result = prime * result + ((project == null) ? 0 : project.hashCode());
-        result = prime * result + Arrays.hashCode(publisher);
-        result = prime * result + ((repositoryURL == null) ? 0 : repositoryURL.hashCode());
-        result = prime * result + Arrays.hashCode(requires);
-        result = prime * result + Arrays.hashCode(source);
-        result = prime * result + ((status == null) ? 0 : status.hashCode());
-        result = prime * result + ((structure == null) ? 0 : structure.hashCode());
-        result = prime * result + ((timeRequired == null) ? 0 : timeRequired.hashCode());
-        result = prime * result + ((title == null) ? 0 : title.hashCode());
-        result = prime * result + ((type == null) ? 0 : type.hashCode());
-        result = prime * result + ((version == null) ? 0 : version.hashCode());
-        return result;
-    }
+        @Override
+        public int hashCode() {
+            return Integer.hashCode(project_id);
+        }
 
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof GitProject)) {
+                return false;
+            }
+            GitProject other = (GitProject) obj;
+            return this.project_id == other.project_id;
         }
-        if (!(obj instanceof SearchResultDTO)) {
-            return false;
+
+        @Override
+        public String toString() {
+            return  "Project: { project_id: " + project_id
+                + ", project_name: " + project_name
+                + ", namespace: " + namespace
+                + ", main_group: " + main_group
+                + ", sub_group: " + sub_group
+                + ", url: " + url
+                + " }";
         }
-        SearchResultDTO other = (SearchResultDTO) obj;
-        return Objects.equals(this.audience, other.audience)
-            && Arrays.equals(this.collectionContent, other.collectionContent)
-            && Arrays.equals(this.contributor, other.contributor)
-            && Arrays.equals(this.creator, other.creator)
-            && this.deprecated == other.deprecated
-            && Objects.equals(this.description, other.description)
-            && Objects.equals(this.difficulty, other.difficulty)
-            && Objects.equals(this.educationLevel, other.educationLevel)
-            && Arrays.equals(this.format, other.format)
-            && Objects.equals(this.gitURL, other.gitURL)
-            && Objects.equals(this.identifier, other.identifier)
-            && Objects.equals(this.image, other.image)
-            && Arrays.equals(this.keyword, other.keyword)
-            && Arrays.equals(this.language, other.language)
-            && Objects.equals(this.license, other.license)
-            && Objects.equals(this.metadataVersion, other.metadataVersion)
-            && Arrays.equals(this.programmingLanguage, other.programmingLanguage)
-            && Objects.equals(this.project, other.project)
-            && Arrays.equals(this.publisher, other.publisher)
-            && Objects.equals(this.repositoryURL, other.repositoryURL)
-            && Arrays.equals(this.requires, other.requires)
-            && Arrays.equals(this.source, other.source)
-            && Objects.equals(this.status, other.status)
-            && Objects.equals(this.structure, other.structure)
-            && Objects.equals(this.timeRequired, other.timeRequired)
-            && Objects.equals(this.title, other.title)
-            && this.type == other.type
-            && Objects.equals(this.version, other.version);
     }
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultsDTO.java b/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultsDTO.java
index 121490daf..9fd9000b2 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultsDTO.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchResultsDTO.java
@@ -1,182 +1,9 @@
 package at.ac.uibk.gitsearch.service.dto;
 
 import java.util.List;
-import java.util.Objects;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonValue;
 
 public class SearchResultsDTO {
 
-	@JsonFormat(shape = JsonFormat.Shape.OBJECT)
-	public enum ExerciseType {
-		PROGRAMMING_EXERCISE("programming exercise"), EXERCISE("exercise"), COLLECTION("collection"), OTHER("other");
-
-		ExerciseType(String externalName) {
-			this.externalName = externalName;
-		}
-
-		private final String externalName;
-
-		@JsonValue
-		public String getExternalName() {
-			return externalName;
-		}
-	}
-
-	public static class Person {
-		private String name;
-		private String affiliation;
-		private String email;
-
-		public String getName() {
-			return name;
-		}
-
-		public void setName(String name) {
-			this.name = name;
-		}
-
-		public String getAffiliation() {
-			return affiliation;
-		}
-
-		public void setAffiliation(String affiliation) {
-			this.affiliation = affiliation;
-		}
-
-		public String getEmail() {
-			return email;
-		}
-
-		public void setEmail(String email) {
-			this.email = email;
-		}
-
-		@Override
-		public String toString() {
-			return "Person: { name: " + this.name + ", affiliation: " + this.affiliation + ", email: " + this.email
-					+ "}";
-		}
-
-		@Override
-		public int hashCode() {
-			final int prime = 31;
-			int result = 1;
-			result = prime * result + ((affiliation == null) ? 0 : affiliation.hashCode());
-			result = prime * result + ((email == null) ? 0 : email.hashCode());
-			result = prime * result + ((name == null) ? 0 : name.hashCode());
-			return result;
-		}
-
-		@Override
-		public boolean equals(Object obj) {
-			if (this == obj) {
-                return true;
-            }
-			if (!(obj instanceof Person)) {
-			    return false;
-            }
-			Person other = (Person) obj;
-			return Objects.equals(this.affiliation, other.affiliation)
-                && Objects.equals(this.email, other.email)
-                && Objects.equals(this.name, other.name);
-		}
-	}
-
-	public static class GitProject {
-		private int project_id;
-		private String project_name;
-		private String namespace;
-		private String main_group;
-		private String sub_group;
-		private String url;
-
-		public int getProject_id() {
-			return project_id;
-		}
-
-		public void setProject_id(int project_id) {
-			this.project_id = project_id;
-		}
-
-        public String getProject_name() {
-            return project_name;
-        }
-
-        public void setProject_name(String project_name) {
-            this.project_name = project_name;
-        }
-
-        public String getNamespace() {
-            return namespace;
-        }
-
-        public void setNamespace(String namespace) {
-            this.namespace = namespace;
-        }
-
-        public String getMain_group() {
-            return main_group;
-        }
-
-        public void setMain_group(String main_group) {
-            this.main_group = main_group;
-        }
-
-        public String getSub_group() {
-            return sub_group;
-        }
-
-        public void setSub_group(String sub_group) {
-            this.sub_group = sub_group;
-        }
-
-        public String getUrl() {
-            return url;
-        }
-
-        public void setUrl(String url) {
-            this.url = url;
-        }
-
-        public int getProjectId() {
-			return project_id;
-		}
-
-		public void setProjectId(int project_id) {
-			this.project_id = project_id;
-		}
-
-		@Override
-		public int hashCode() {
-		    return Integer.hashCode(project_id);
-		}
-
-		@Override
-		public boolean equals(Object obj) {
-			if (this == obj) {
-                return true;
-            }
-			if (!(obj instanceof GitProject)) {
-			    return false;
-            }
-			GitProject other = (GitProject) obj;
-			return this.project_id == other.project_id;
-		}
-
-        @Override
-        public String toString() {
-            return  "Project: { project_id: " + project_id
-                + ", project_name: " + project_name
-                + ", namespace: " + namespace
-                + ", main_group: " + main_group
-                + ", sub_group: " + sub_group
-                + ", url: " + url
-                + " }";
-        }
-    }
-
 	private List<SearchResultDTO> searchResult;
 
 	/**
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/dto/UserProvidedMetadataDTO.java b/src/main/java/at/ac/uibk/gitsearch/service/dto/UserProvidedMetadataDTO.java
new file mode 100644
index 000000000..f0ca107c2
--- /dev/null
+++ b/src/main/java/at/ac/uibk/gitsearch/service/dto/UserProvidedMetadataDTO.java
@@ -0,0 +1,471 @@
+package at.ac.uibk.gitsearch.service.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public class UserProvidedMetadataDTO {
+
+    public UserProvidedMetadataDTO() {
+        // default constructor
+    }
+
+    private String metadataVersion; // just for YAML test data reader
+    private String title;
+    private String identifier;
+    private String version;
+    private String structure;
+
+    private String description;
+    private ExerciseType type;
+    private String license;
+    private String[] keyword;
+
+    private String[] format;
+    private String[] programmingLanguage;
+    private String[] language;
+    private String status;
+
+    private String educationLevel;
+
+    @JsonIgnore
+    private String audience;
+
+    private String timeRequired;
+
+    @JsonIgnore
+    private String[] collectionContent;
+
+    private Person[] creator;
+    private Person[] publisher;
+    private Person[] contributor;
+
+    private boolean deprecated;
+
+    private String difficulty;
+    private String[] source;
+
+    private String[] requires;
+    private String image;
+
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public ExerciseType getType() {
+        return type;
+    }
+
+    public void setType(ExerciseType type) {
+        this.type = type;
+    }
+
+    public String[] getLanguage() {
+        return language;
+    }
+
+    public void setLanguage(String[] language) {
+        this.language = language;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getEducationLevel() {
+        return educationLevel;
+    }
+
+    public void setEducationLevel(String educationLevel) {
+        this.educationLevel = educationLevel;
+    }
+
+    public String getAudience() {
+        return audience;
+    }
+
+    public void setAudience(String audience) {
+        this.audience = audience;
+    }
+
+    public String getTimeRequired() {
+        return timeRequired;
+    }
+
+    public void setTimeRequired(String timeRequired) {
+        this.timeRequired = timeRequired;
+    }
+
+    public Person[] getCreator() {
+        return creator;
+    }
+
+    public void setCreator(Person[] creator) {
+        this.creator = creator;
+    }
+
+    public Person[] getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(Person[] publisher) {
+        this.publisher = publisher;
+    }
+
+    public Person[] getContributor() {
+        return contributor;
+    }
+
+    public void setContributor(Person[] contributor) {
+        this.contributor = contributor;
+    }
+
+    public boolean isDeprecated() {
+        return deprecated;
+    }
+
+    public void setDeprecated(boolean deprecated) {
+        this.deprecated = deprecated;
+    }
+
+    public String getDifficulty() {
+        return difficulty;
+    }
+
+    public void setDifficulty(String difficulty) {
+        this.difficulty = difficulty;
+    }
+
+    public String[] getSource() {
+        return source;
+    }
+
+    public void setSource(String[] source) {
+        this.source = source;
+    }
+
+    public String[] getRequires() {
+        return requires;
+    }
+
+    public void setRequires(String[] requires) {
+        this.requires = requires;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    private String repositoryURL;
+
+    public String getMetadataVersion() {
+        return metadataVersion;
+    }
+
+    public void setMetadataVersion(String metadataVersion) {
+        this.metadataVersion = metadataVersion;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getLicense() {
+        return license;
+    }
+
+    public void setLicense(String license) {
+        this.license = license;
+    }
+
+    public String[] getKeyword() {
+        return keyword;
+    }
+
+    public void setKeyword(String[] keyword) {
+        this.keyword = keyword;
+    }
+
+    public String[] getFormat() {
+        return format;
+    }
+
+    public void setFormat(String[] format) {
+        this.format = format;
+    }
+
+    public String[] getProgrammingLanguage() {
+        return programmingLanguage;
+    }
+
+    public void setProgrammingLanguage(String[] programmingLanguage) {
+        this.programmingLanguage = programmingLanguage;
+    }
+
+    public String getRepositoryURL() {
+        return repositoryURL;
+    }
+
+    public void setRepositoryURL(String repositoryURL) {
+        this.repositoryURL = repositoryURL;
+    }
+
+    public String getStructure() {
+        return structure;
+    }
+
+    public void setStructure(String structure) {
+        this.structure = structure;
+    }
+
+    /**
+     * clone constructor
+     *
+     * @param toClone
+     */
+    public UserProvidedMetadataDTO(UserProvidedMetadataDTO toClone) {
+        super();
+        this.metadataVersion = toClone.metadataVersion;
+        this.title = toClone.title;
+        this.identifier = toClone.identifier;
+        this.version = toClone.version;
+        this.structure = toClone.structure;
+        this.description = toClone.description;
+        this.type = toClone.type;
+        this.license = toClone.license;
+        this.keyword = toClone.keyword;
+        this.format = toClone.format;
+        this.programmingLanguage = toClone.programmingLanguage;
+        this.language = toClone.language;
+        this.status = toClone.status;
+        this.educationLevel = toClone.educationLevel;
+        this.audience = toClone.audience;
+        this.timeRequired = toClone.timeRequired;
+        this.collectionContent = toClone.collectionContent;
+        this.creator = toClone.creator;
+        this.publisher = toClone.publisher;
+        this.contributor = toClone.contributor;
+        this.deprecated = toClone.deprecated;
+        this.difficulty = toClone.difficulty;
+        this.source = toClone.source;
+        this.requires = toClone.requires;
+        this.image = toClone.image;
+        this.repositoryURL = toClone.repositoryURL;
+    }
+
+    @Override
+    public String toString() {
+        return "UserProvidedMetadataDTO { audience: " + audience
+            + ", collectionContent: " + Arrays.toString(collectionContent)
+            + ", contributor: " + Arrays.toString(contributor)
+            + ", creator: " + Arrays.toString(creator)
+            + ", deprecated: " + deprecated
+            + ", description: " + description
+            + ", difficulty: " + difficulty
+            + ", educationLevel: " + educationLevel
+            + ", format: " + Arrays.toString(format)
+            + ", identifier:" + identifier
+            + ", image: " + image
+            + ", keyword: " + Arrays.toString(keyword)
+            + ", language: " + Arrays.toString(language)
+            + ", license: " + license
+            + ", metadataVersion: " + metadataVersion
+            + ", programmingLanguage: " + Arrays.toString(programmingLanguage)
+            + ", publisher: " + Arrays.toString(publisher)
+            + ", repositoryURL: " + repositoryURL
+            + ", requires: " + Arrays.toString(requires)
+            + ", source: " + Arrays.toString(source)
+            + ", status: " + status
+            + ", structure: " + structure
+            + ", timeRequired: " + timeRequired
+            + ", title: " + title
+            + ", type: " + type
+            + ", version: " + version
+            + " }";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((audience == null) ? 0 : audience.hashCode());
+        result = prime * result + Arrays.hashCode(collectionContent);
+        result = prime * result + Arrays.hashCode(contributor);
+        result = prime * result + Arrays.hashCode(creator);
+        result = prime * result + (deprecated ? 1231 : 1237);
+        result = prime * result + ((description == null) ? 0 : description.hashCode());
+        result = prime * result + ((difficulty == null) ? 0 : difficulty.hashCode());
+        result = prime * result + ((educationLevel == null) ? 0 : educationLevel.hashCode());
+        result = prime * result + Arrays.hashCode(format);
+        result = prime * result + ((identifier == null) ? 0 : identifier.hashCode());
+        result = prime * result + ((image == null) ? 0 : image.hashCode());
+        result = prime * result + Arrays.hashCode(keyword);
+        result = prime * result + Arrays.hashCode(language);
+        result = prime * result + ((license == null) ? 0 : license.hashCode());
+        result = prime * result + ((metadataVersion == null) ? 0 : metadataVersion.hashCode());
+        result = prime * result + Arrays.hashCode(programmingLanguage);
+        result = prime * result + Arrays.hashCode(publisher);
+        result = prime * result + ((repositoryURL == null) ? 0 : repositoryURL.hashCode());
+        result = prime * result + Arrays.hashCode(requires);
+        result = prime * result + Arrays.hashCode(source);
+        result = prime * result + ((status == null) ? 0 : status.hashCode());
+        result = prime * result + ((structure == null) ? 0 : structure.hashCode());
+        result = prime * result + ((timeRequired == null) ? 0 : timeRequired.hashCode());
+        result = prime * result + ((title == null) ? 0 : title.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        result = prime * result + ((version == null) ? 0 : version.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof UserProvidedMetadataDTO)) {
+            return false;
+        }
+        UserProvidedMetadataDTO other = (UserProvidedMetadataDTO) obj;
+        return Objects.equals(this.audience, other.audience)
+            && Arrays.equals(this.collectionContent, other.collectionContent)
+            && Arrays.equals(this.contributor, other.contributor)
+            && Arrays.equals(this.creator, other.creator)
+            && this.deprecated == other.deprecated
+            && Objects.equals(this.description, other.description)
+            && Objects.equals(this.difficulty, other.difficulty)
+            && Objects.equals(this.educationLevel, other.educationLevel)
+            && Arrays.equals(this.format, other.format)
+            && Objects.equals(this.identifier, other.identifier)
+            && Objects.equals(this.image, other.image)
+            && Arrays.equals(this.keyword, other.keyword)
+            && Arrays.equals(this.language, other.language)
+            && Objects.equals(this.license, other.license)
+            && Objects.equals(this.metadataVersion, other.metadataVersion)
+            && Arrays.equals(this.programmingLanguage, other.programmingLanguage)
+            && Arrays.equals(this.publisher, other.publisher)
+            && Objects.equals(this.repositoryURL, other.repositoryURL)
+            && Arrays.equals(this.requires, other.requires)
+            && Arrays.equals(this.source, other.source)
+            && Objects.equals(this.status, other.status)
+            && Objects.equals(this.structure, other.structure)
+            && Objects.equals(this.timeRequired, other.timeRequired)
+            && Objects.equals(this.title, other.title)
+            && this.type == other.type
+            && Objects.equals(this.version, other.version);
+    }
+
+
+    @JsonFormat(shape = JsonFormat.Shape.OBJECT)
+    public enum ExerciseType {
+        PROGRAMMING_EXERCISE("programming exercise"), EXERCISE("exercise"), COLLECTION("collection"), OTHER("other");
+
+        ExerciseType(String externalName) {
+            this.externalName = externalName;
+        }
+
+        private final String externalName;
+
+        @JsonValue
+        public String getExternalName() {
+            return externalName;
+        }
+    }
+
+    public static class Person {
+        private String name;
+        private String affiliation;
+        private String email;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getAffiliation() {
+            return affiliation;
+        }
+
+        public void setAffiliation(String affiliation) {
+            this.affiliation = affiliation;
+        }
+
+        public String getEmail() {
+            return email;
+        }
+
+        public void setEmail(String email) {
+            this.email = email;
+        }
+
+        @Override
+        public String toString() {
+            return "Person: { name: " + this.name
+                + ", affiliation: " + this.affiliation
+                + ", email: " + this.email
+                + " }";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((affiliation == null) ? 0 : affiliation.hashCode());
+            result = prime * result + ((email == null) ? 0 : email.hashCode());
+            result = prime * result + ((name == null) ? 0 : name.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof Person)) {
+                return false;
+            }
+            Person other = (Person) obj;
+            return Objects.equals(this.affiliation, other.affiliation)
+                && Objects.equals(this.email, other.email)
+                && Objects.equals(this.name, other.name);
+        }
+    }
+}
+
-- 
GitLab