diff --git a/.dockerignore b/.dockerignore
index 1dff951bc9ce1517c835779410a3c26e758d09ff..5ae5188a01e2725fe79a442a0065293c12ff2877 100755
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,5 +1,9 @@
+.github
 .idea
-src
+LICENSE
+README.ms
+docs
 target/**
 !target/fdp-spring-boot.jar
 !target/classes/application-production.yml
+nb-configuration.xml
diff --git a/.gitignore b/.gitignore
index 871d9d423cda8effd646523ef1802859c728f355..cf4b4672801ff63a1ef34d32c284e97358b6924c 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
 # Java
-
+/target
 # Netbeans
 nb-configuration\.xml
 /nbproject/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6ae5d4ed2d4b2a31468314472de021fce3075f21..4a534690ea567dcb7552d97f94319f93a1cfde7c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@ stages:
 
 build:
   stage: build
-  image: maven:3.6.3-jdk-11
+  image: maven:3.6.3-openjdk-16-slim
   only:
     - master
   script:
diff --git a/Dockerfile b/Dockerfile
index 5d383e84e7808e2fc84d24500ed859cb13005cbc..1d9128d769ff5af7627ad4472a6d339cb7899dd7 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,12 +21,23 @@
 # THE SOFTWARE.
 #
 
-FROM openjdk:11-jdk-slim
+# BUILD STAGE
+FROM maven:3-openjdk-16 as builder
+
+WORKDIR /builder
+
+ADD . /builder
+
+RUN mvn --quiet -B -U --fail-fast -DskipTests package
+
+################################################################################
+# RUN STAGE
+FROM openjdk:16-jdk-slim
 
 WORKDIR /fdp
 
-ADD target/fdp-spring-boot.jar /fdp/app.jar
-ADD target/classes/application-production.yml /fdp/application.yml
+COPY --from=builder /builder/target/fdp-spring-boot.jar /fdp/app.jar
+COPY --from=builder /builder/target/classes/application-production.yml /fdp/application.yml
 
 COPY ./start.sh /start.sh
 
diff --git a/pom.xml b/pom.xml
index a72678daba0fa379907ab07ceb509950281f9cfc..f2453326aba555365c1e21156decfbab7d40ec03 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.1.10.RELEASE</version>
+        <version>2.5.3</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>nl.dtls</groupId>
     <artifactId>fairdatapoint</artifactId>
-    <version>1.3.0</version>
+    <version>1.12.0</version>
     <packaging>jar</packaging>
 
     <name>FairDataPoint</name>
@@ -25,20 +25,20 @@
 
     <developers>
         <developer>
-            <name>Rajaram Kaliyaperumal</name>
-            <email>rr.kaliyaperumal@gmail.com</email>
-            <organization>LUMC</organization>
-            <organizationUrl>https://www.lumc.nl/</organizationUrl>
+            <name>Vojtech Knaisl</name>
+            <email>vknaisl@gmail.com</email>
+            <url>https://github.com/vknaisl</url>
+        </developer>
+        <developer>
+            <name>Marek Suchánek</name>
+            <url>https://github.com/MarekSuchanek</url>
         </developer>
         <developer>
             <name>Kees Burger</name>
             <email>kees.burger@dtls.nl</email>
             <organization>DTLS</organization>
             <organizationUrl>http://www.dtls.nl/</organizationUrl>
-        </developer>
-        <developer>
-            <name>Vojtech Knaisl</name>
-            <email>vknaisl@gmail.com</email>
+            <url>https://github.com/kburger</url>
         </developer>
     </developers>
 
@@ -47,41 +47,31 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
         <!-- Maven -->
-        <maven.compiler.source>11</maven.compiler.source>
-        <maven.compiler.target>11</maven.compiler.target>
+        <maven.compiler.source>16</maven.compiler.source>
+        <maven.compiler.target>16</maven.compiler.target>
 
         <!-- Project related -->
-        <spring.rdf.migration.version>1.0.0.RELEASE</spring.rdf.migration.version>
-        <spring.security.acl.mongo.version>5.1.5.RELEASE</spring.security.acl.mongo.version>
-
-        <!-- Spring -->
-        <spring.boot.handlebars.version>0.3.0</spring.boot.handlebars.version>
+        <spring.rdf.migration.version>1.1.0.RELEASE</spring.rdf.migration.version>
+        <spring.security.acl.mongo.version>5.2.4.RELEASE</spring.security.acl.mongo.version>
 
         <!-- Core -->
-        <mongobee.version>0.13</mongobee.version>
-        <springfox.swagger.version>2.9.1</springfox.swagger.version>
-        <logback.version>1.2.3</logback.version>
-        <jackson.version>2.10.3</jackson.version>
-        <rdf4j.version>3.0.0</rdf4j.version>
-        <velocity.version>1.7</velocity.version>
+        <springdoc.version>1.5.2</springdoc.version>
+        <mongock.version>4.3.8</mongock.version>
+        <mongodb.driver-sync.version>4.2.3</mongodb.driver-sync.version>
+        <mongodb.spring-data.v3.version>3.2.3</mongodb.spring-data.v3.version>
+        <rdf4j.version>3.7.2</rdf4j.version>
         <unirest.version>1.4.9</unirest.version>
-        <sass.version>5.3.0</sass.version>
-        <jwt.version>0.10.5</jwt.version>
-        <lombok.version>1.18.10</lombok.version>
-
-        <!-- Test -->
-        <junit-jupiter.version>5.3.2</junit-jupiter.version>
-        <mockito.version>2.23.0</mockito.version>
+        <jwt.version>0.11.2</jwt.version>
+        <lombok.version>1.18.20</lombok.version>
+        <rdf-resolver.version>0.1.2-SNAPSHOT</rdf-resolver.version>
 
         <!-- Plugins -->
-        <plugin.license.version>3.0</plugin.license.version>
+        <plugin.license.version>4.1</plugin.license.version>
         <plugin.jacoco.version>0.7.6.201602180812</plugin.jacoco.version>
         <plugin.coveralls.version>4.3.0</plugin.coveralls.version>
         <plugin.javax_xml_bind.version>2.3.1</plugin.javax_xml_bind.version>
-        <plugin.git_commit_id.version>2.2.4</plugin.git_commit_id.version>
+        <plugin.git_commit_id.version>4.9.10</plugin.git_commit_id.version>
         <plugin.rdf4j_generator.version>0.2.0</plugin.rdf4j_generator.version>
-
-
     </properties>
 
     <repositories>
@@ -90,16 +80,39 @@
             <name>Central Repository</name>
             <url>https://repo.maven.apache.org/maven2</url>
             <layout>default</layout>
-            <releases><enabled>true</enabled><updatePolicy>always</updatePolicy></releases>
-            <snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots>
+            <releases>
+                <enabled>true</enabled>
+                <updatePolicy>always</updatePolicy>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+                <updatePolicy>always</updatePolicy>
+            </snapshots>
         </repository>
         <repository>
             <id>nexus-releases</id>
             <name>Nexus Releases</name>
             <url>https://nexus.internal.fairdatapoint.org/repository/maven-releases/</url>
         </repository>
+        <repository>
+            <id>nexus-snapshots</id>
+            <name>Nexus Snapshots</name>
+            <url>https://nexus.internal.fairdatapoint.org/repository/maven-snapshots/</url>
+        </repository>
     </repositories>
 
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.github.cloudyrock.mongock</groupId>
+                <artifactId>mongock-bom</artifactId>
+                <version>${mongock.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
     <dependencies>
 
         <!-- ////////////////// -->
@@ -148,6 +161,14 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-acl-mongodb</artifactId>
@@ -155,27 +176,40 @@
         </dependency>
 
         <!-- ////////////////// -->
-        <!--   Core             -->
+        <!--   Mongock          -->
         <!-- ////////////////// -->
         <dependency>
-            <groupId>com.github.mongobee</groupId>
-            <artifactId>mongobee</artifactId>
-            <version>${mongobee.version}</version>
+            <groupId>com.github.cloudyrock.mongock</groupId>
+            <artifactId>mongock-spring-v5</artifactId>
         </dependency>
         <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger2</artifactId>
-            <version>${springfox.swagger.version}</version>
+            <groupId>com.github.cloudyrock.mongock</groupId>
+            <artifactId>mongodb-springdata-v3-driver</artifactId>
         </dependency>
         <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger-ui</artifactId>
-            <version>${springfox.swagger.version}</version>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongodb-driver-sync</artifactId>
+            <version>${mongodb.driver-sync.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-mongodb</artifactId>
+            <version>${mongodb.spring-data.v3.version}</version>
+        </dependency>
+
+        <!-- ////////////////// -->
+        <!--   Core             -->
+        <!-- ////////////////// -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <version>${springdoc.version}</version>
         </dependency>
         <dependency>
             <groupId>org.eclipse.rdf4j</groupId>
             <artifactId>rdf4j-runtime</artifactId>
             <version>${rdf4j.version}</version>
+            <type>pom</type>
             <exclusions>
                 <exclusion>
                     <groupId>ch.qos.logback</groupId>
@@ -193,16 +227,6 @@
             <artifactId>rdf4j-sail-nativerdf</artifactId>
             <version>${rdf4j.version}</version>
         </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-annotations</artifactId>
-            <version>${jackson.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
-            <version>${jackson.version}</version>
-        </dependency>
         <dependency>
             <groupId>com.mashape.unirest</groupId>
             <artifactId>unirest-java</artifactId>
@@ -230,6 +254,16 @@
             <artifactId>lombok</artifactId>
             <version>${lombok.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.github.fairdevkit</groupId>
+            <artifactId>rdf-resource-resolver-core</artifactId>
+            <version>${rdf-resolver.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fairdevkit</groupId>
+            <artifactId>rdf-resource-resolver-api</artifactId>
+            <version>${rdf-resolver.version}</version>
+        </dependency>
 
         <!-- ////////////////// -->
         <!--   Test             -->
@@ -243,10 +277,6 @@
                     <groupId>com.vaadin.external.google</groupId>
                     <artifactId>android-json</artifactId>
                 </exclusion>
-                <exclusion>
-                    <groupId>junit</groupId>
-                    <artifactId>junit</artifactId>
-                </exclusion>
             </exclusions>
         </dependency>
         <dependency>
@@ -254,30 +284,6 @@
             <artifactId>spring-security-test</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-engine</artifactId>
-            <version>${junit-jupiter.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-params</artifactId>
-            <version>${junit-jupiter.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>${mockito.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-junit-jupiter</artifactId>
-            <version>${mockito.version}</version>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
     <build>
@@ -292,6 +298,9 @@
                     <properties>
                         <owner>DTL</owner>
                     </properties>
+                    <mapping>
+                        <java>JAVADOC_STYLE</java>
+                    </mapping>
                     <excludes>
                         <exclude>pom.xml</exclude>
                         <exclude>**/maven.config</exclude>
@@ -301,8 +310,10 @@
                         <exclude>**/*.properties</exclude>
                         <exclude>**/*.xml</exclude>
                         <exclude>**/*.json</exclude>
+                        <exclude>**/*.json</exclude>
                         <exclude>LICENSE</exclude>
                         <exclude>.dockerignore</exclude>
+                        <exclude>Dockerfile.build</exclude>
                     </excludes>
                 </configuration>
                 <executions>
@@ -367,6 +378,21 @@
                     <verbose>false</verbose>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>**/*.java</include>
+                    </includes>
+                    <excludes>
+                        <exclude>**/*Fixtures.java</exclude>
+                        <exclude>**/*Config.java</exclude>
+                        <exclude>**/Common.java</exclude>
+                        <exclude>**/common/*.java</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
             <plugin>
                 <groupId>com.github.kburger</groupId>
                 <artifactId>rdf4j-generator-maven-plugin</artifactId>
@@ -395,7 +421,7 @@
                             <prefix>fdp</prefix>
                         </vocabulary>
                         <vocabulary>
-                            <url>https://raw.githubusercontent.com/schemaorg/schemaorg/master/data/releases/3.4/schema.ttl</url>
+                            <url>https://raw.githubusercontent.com/schemaorg/schemaorg/main/data/releases/3.4/schema.ttl</url>
                             <namespace>http://schema.org/</namespace>
                             <prefix>schemaOrg</prefix>
                         </vocabulary>
diff --git a/src/main/java/nl/dtls/fairdatapoint/Application.java b/src/main/java/nl/dtls/fairdatapoint/Application.java
index 7c82908e6da1ba1e493c094554273c8b2341d732..c57a3c75266950c38a9b11aded6540e8df74974f 100755
--- a/src/main/java/nl/dtls/fairdatapoint/Application.java
+++ b/src/main/java/nl/dtls/fairdatapoint/Application.java
@@ -24,6 +24,7 @@ package nl.dtls.fairdatapoint;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -32,6 +33,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 @EnableWebMvc
 @EnableAsync
 @ComponentScan(basePackages = "nl.dtls.fairdatapoint.*")
+@ConfigurationPropertiesScan("nl.dtls.fairdatapoint.config.*")
 public class Application {
 
     public static void main(String[] args) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/apikey/ApiKeyController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/apikey/ApiKeyController.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f1fadc3bd3a52df7bf1b87cafb638ab166dd605
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/apikey/ApiKeyController.java
@@ -0,0 +1,71 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.apikey;
+
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.apikey.ApiKeyDTO;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.service.apikey.ApiKeyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static java.lang.String.format;
+
+@Tag(name = "Authentication and Authorization")
+@RestController
+@RequestMapping("/api-keys")
+public class ApiKeyController {
+
+    @Autowired
+    private ApiKeyService apiKeyService;
+
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<ApiKeyDTO>> getApiKeys() {
+        List<ApiKeyDTO> dto = apiKeyService.getAll();
+        return new ResponseEntity<>(dto, HttpStatus.OK);
+    }
+
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<ApiKeyDTO> createApiKey() {
+        ApiKeyDTO dto = apiKeyService.create();
+        return new ResponseEntity<>(dto, HttpStatus.CREATED);
+    }
+
+    @DeleteMapping("/{uuid}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<Void> deleteShape(@PathVariable final String uuid) throws ResourceNotFoundException {
+        boolean result = apiKeyService.delete(uuid);
+        if (result) {
+            return ResponseEntity.noContent().build();
+        } else {
+            throw new ResourceNotFoundException(format("Api Key '%s' doesn't exist", uuid));
+        }
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/config/ConfigController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/config/ConfigController.java
index 716cb3ba7468ca68419da0f20cba3b5ed6d821e9..e36cde873ee143f646f07a660a0387766810a956 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/config/ConfigController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/config/ConfigController.java
@@ -22,15 +22,19 @@
  */
 package nl.dtls.fairdatapoint.api.controller.config;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
 import nl.dtls.fairdatapoint.api.dto.config.BootstrapConfigDTO;
 import nl.dtls.fairdatapoint.service.config.ConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
 
+@Tag(name = "Client")
 @RestController
 @RequestMapping("/configs")
 public class ConfigController {
@@ -38,7 +42,7 @@ public class ConfigController {
     @Autowired
     private ConfigService configService;
 
-    @RequestMapping(value = "/bootstrap", method = RequestMethod.GET)
+    @GetMapping(path = "/bootstrap", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<BootstrapConfigDTO> getBootstrapConfig() {
         BootstrapConfigDTO dto = configService.getBootstrapConfig();
         return new ResponseEntity<>(dto, HttpStatus.OK);
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/dashboard/DashboardController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/dashboard/DashboardController.java
index 59c479176b940341735fcdc3f69184c8305b4c23..3eaa1a36162018c6da223f4c775a56385e10297a 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/dashboard/DashboardController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/dashboard/DashboardController.java
@@ -22,6 +22,7 @@
  */
 package nl.dtls.fairdatapoint.api.controller.dashboard;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
 import nl.dtls.fairdatapoint.api.dto.dashboard.DashboardItemDTO;
 import nl.dtls.fairdatapoint.service.dashboard.DashboardService;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
@@ -29,9 +30,10 @@ import org.eclipse.rdf4j.model.IRI;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
@@ -41,6 +43,7 @@ import static nl.dtls.fairdatapoint.util.HttpUtil.getRequestURL;
 import static nl.dtls.fairdatapoint.util.RdfUtil.removeLastPartOfIRI;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
+@Tag(name = "Client")
 @RestController
 @RequestMapping("/dashboard")
 public class DashboardController {
@@ -52,7 +55,7 @@ public class DashboardController {
     @Autowired
     private DashboardService dashboardService;
 
-    @RequestMapping(method = RequestMethod.GET)
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<List<DashboardItemDTO>> getDashboard(HttpServletRequest request) throws MetadataServiceException {
         IRI uri = i(getRequestURL(request, persistentUrl));
         IRI repositoryUri = removeLastPartOfIRI(uri);
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/exception/ExceptionControllerAdvice.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/exception/ExceptionControllerAdvice.java
index fdc2886a533b5c3f2e5ed8511644068ccf633a67..c07683efc8ce22d7cfcb3acbb93598ed6594cde0 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/exception/ExceptionControllerAdvice.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/exception/ExceptionControllerAdvice.java
@@ -27,15 +27,21 @@
  */
 package nl.dtls.fairdatapoint.api.controller.exception;
 
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import lombok.extern.slf4j.Slf4j;
 import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
 import nl.dtls.fairdatapoint.entity.exception.*;
+import nl.dtls.fairdatapoint.entity.index.exception.IndexException;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.rio.RDFFormat;
 import org.eclipse.rdf4j.rio.Rio;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.web.bind.annotation.ControllerAdvice;
@@ -55,7 +61,15 @@ public class ExceptionControllerAdvice {
 
     @ExceptionHandler({ValidationException.class})
     @ResponseStatus(HttpStatus.BAD_REQUEST)
-    @ResponseBody
+    @ResponseBody()
+    @ApiResponse(
+        responseCode = "400",
+        description = "Bad request",
+        content = @Content(
+            mediaType = MediaType.APPLICATION_JSON_VALUE,
+            schema = @Schema(implementation = ErrorDTO.class)
+        )
+    )
     public ErrorDTO handleBadRequest(Exception e) {
         log.warn(e.getMessage());
         return new ErrorDTO(HttpStatus.BAD_REQUEST, e.getMessage());
@@ -64,6 +78,14 @@ public class ExceptionControllerAdvice {
     @ExceptionHandler({RdfValidationException.class})
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     @ResponseBody
+    @ApiResponse(
+            responseCode = "400",
+            description = "Bad request",
+            content = @Content(
+                    mediaType = MediaType.APPLICATION_JSON_VALUE,
+                    schema = @Schema(implementation = ErrorDTO.class)
+            )
+    )
     public Model handleBadRequest(RdfValidationException e) {
         Model validationReportModel = e.getModel();
 
@@ -83,6 +105,14 @@ public class ExceptionControllerAdvice {
     @ExceptionHandler({BadCredentialsException.class, UnauthorizedException.class})
     @ResponseStatus(HttpStatus.UNAUTHORIZED)
     @ResponseBody
+    @ApiResponse(
+            responseCode = "401",
+            description = "Unauthorized",
+            content = @Content(
+                    mediaType = MediaType.APPLICATION_JSON_VALUE,
+                    schema = @Schema(implementation = ErrorDTO.class)
+            )
+    )
     public ErrorDTO handleUnauthorized(Exception e) {
         log.error(e.getMessage());
         return new ErrorDTO(HttpStatus.UNAUTHORIZED, e.getMessage());
@@ -91,6 +121,14 @@ public class ExceptionControllerAdvice {
     @ExceptionHandler({ForbiddenException.class, AccessDeniedException.class})
     @ResponseStatus(HttpStatus.FORBIDDEN)
     @ResponseBody
+    @ApiResponse(
+            responseCode = "403",
+            description = "Forbidden",
+            content = @Content(
+                    mediaType = MediaType.APPLICATION_JSON_VALUE,
+                    schema = @Schema(implementation = ErrorDTO.class)
+            )
+    )
     public ErrorDTO handleForbidden(Exception e) {
         log.error(e.getMessage());
         return new ErrorDTO(HttpStatus.FORBIDDEN, e.getMessage());
@@ -99,6 +137,14 @@ public class ExceptionControllerAdvice {
     @ExceptionHandler(ResourceNotFoundException.class)
     @ResponseStatus(HttpStatus.NOT_FOUND)
     @ResponseBody
+    @ApiResponse(
+            responseCode = "404",
+            description = "Resource Not Found",
+            content = @Content(
+                    mediaType = MediaType.APPLICATION_JSON_VALUE,
+                    schema = @Schema(implementation = ErrorDTO.class)
+            )
+    )
     public ErrorDTO handleResourceNotFound(ResourceNotFoundException e) {
         log.error(e.getMessage());
         return new ErrorDTO(HttpStatus.NOT_FOUND, e.getMessage());
@@ -107,9 +153,22 @@ public class ExceptionControllerAdvice {
     @ExceptionHandler({MetadataServiceException.class})
     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
     @ResponseBody
+    @ApiResponse(
+            responseCode = "500",
+            description = "Internal Server Error",
+            content = @Content(
+                    mediaType = MediaType.APPLICATION_JSON_VALUE,
+                    schema = @Schema(implementation = ErrorDTO.class)
+            )
+    )
     public ErrorDTO handleInternalServerError(Exception e) {
         log.error(e.getMessage());
         return new ErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
     }
 
+    @ExceptionHandler(IndexException.class)
+    public ResponseEntity<ErrorDTO> handleIndexException(IndexException exception) {
+        return new ResponseEntity<>(exception.getErrorDTO(), exception.getStatus());
+    }
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexAdminController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexAdminController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a49e248a3f4b3ec5f1d5de8eade0d70ec6e58efd
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexAdminController.java
@@ -0,0 +1,88 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.index;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.log4j.Log4j2;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.service.UtilityService;
+import nl.dtls.fairdatapoint.service.index.event.EventService;
+import nl.dtls.fairdatapoint.service.index.webhook.WebhookService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.UUID;
+
+@Tag(name = "Index")
+@Log4j2
+@RestController
+@RequestMapping("/index/admin")
+public class IndexAdminController {
+
+    @Autowired
+    private UtilityService utilityService;
+
+    @Autowired
+    private EventService eventService;
+
+    @Autowired
+    private WebhookService webhookService;
+
+    @Operation(hidden = true)
+    @PostMapping("/trigger")
+    @PreAuthorize("hasRole('ADMIN')")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void triggerMetadataRetrieve(@RequestBody @Valid PingDTO reqDto, HttpServletRequest request) {
+        log.info("Received ping from {}", utilityService.getRemoteAddr(request));
+        final Event event = eventService.acceptAdminTrigger(request, reqDto);
+        webhookService.triggerWebhooks(event);
+        eventService.triggerMetadataRetrieval(event);
+    }
+
+    @Operation(hidden = true)
+    @PostMapping("/trigger-all")
+    @PreAuthorize("hasRole('ADMIN')")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void triggerMetadataRetrieveAll(HttpServletRequest request) {
+        log.info("Received ping from {}", utilityService.getRemoteAddr(request));
+        final Event event = eventService.acceptAdminTriggerAll(request);
+        webhookService.triggerWebhooks(event);
+        eventService.triggerMetadataRetrieval(event);
+    }
+
+    @Operation(hidden = true)
+    @PostMapping("/ping-webhook")
+    @PreAuthorize("hasRole('ADMIN')")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void webhookPing(@RequestParam(required = true) UUID webhook, HttpServletRequest request) {
+        log.info("Received webhook {} ping trigger from {}", webhook, utilityService.getRemoteAddr(request));
+        final Event event = webhookService.handleWebhookPing(request, webhook);
+        webhookService.triggerWebhooks(event);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexEntryController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexEntryController.java
new file mode 100644
index 0000000000000000000000000000000000000000..56743fbda7ba16f2884134d5567b16ac98ed48fb
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexEntryController.java
@@ -0,0 +1,74 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.index;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDetailDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryInfoDTO;
+import nl.dtls.fairdatapoint.service.index.entry.IndexEntryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Optional;
+
+@Tag(name = "Index")
+@RestController
+@RequestMapping("/index/entries")
+public class IndexEntryController {
+
+    @Autowired
+    private IndexEntryService service;
+
+    @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public Page<IndexEntryDTO> getEntriesPage(Pageable pageable,
+                                              @RequestParam(required = false, defaultValue = "") String state) {
+        return service.getEntriesPageDTOs(pageable, state);
+    }
+
+    @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public Optional<IndexEntryDetailDTO> getEntry(@PathVariable final String uuid) {
+        return service.getEntryDetailDTO(uuid);
+    }
+
+    @DeleteMapping("/{uuid}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void deleteEntry(@PathVariable final String uuid) {
+        service.deleteEntry(uuid);
+    }
+
+    @GetMapping(path = "/all", produces = MediaType.APPLICATION_JSON_VALUE)
+    public List<IndexEntryDTO> getEntriesAll() {
+        return service.getAllEntriesAsDTOs();
+    }
+
+    @GetMapping(path = "/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public IndexEntryInfoDTO getEntriesInfo() {
+        return service.getEntriesInfo();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexPingController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexPingController.java
new file mode 100644
index 0000000000000000000000000000000000000000..368b64da683a30f8a573baae55136b80b4150e21
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexPingController.java
@@ -0,0 +1,101 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.index;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.ExampleObject;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.service.UtilityService;
+import nl.dtls.fairdatapoint.service.index.event.EventService;
+import nl.dtls.fairdatapoint.service.index.harvester.HarvesterService;
+import nl.dtls.fairdatapoint.service.index.webhook.WebhookService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+@Tag(name = "Index")
+@RestController
+@RequestMapping("/")
+public class IndexPingController {
+    private static final Logger logger = LoggerFactory.getLogger(IndexPingController.class);
+
+    @Autowired
+    private EventService eventService;
+
+    @Autowired
+    private WebhookService webhookService;
+
+    @Autowired
+    private HarvesterService harvesterService;
+    
+    @Autowired
+    private UtilityService utilityService;
+
+    @Operation(
+            description = "Inform about running FAIR Data Point. It is expected to send pings regularly (at least weekly). There is a rate limit set both per single IP within a period of time and per URL in message.",
+            requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
+                    description = "Ping payload with FAIR Data Point info",
+                    required = true,
+                    content = @Content(
+                            mediaType = "application/json",
+                            examples = {
+                                    @ExampleObject(value = "{\"clientUrl\": \"https://example.com\"}")
+                            },
+                            schema = @Schema(
+                                    type = "object",
+                                    title = "Ping",
+                                    implementation = PingDTO.class
+                            )
+                    )
+            ),
+            responses = {
+                    @ApiResponse(responseCode = "204", description = "Ping accepted (no content)"),
+                    @ApiResponse(responseCode = "400", description = "Invalid ping format"),
+                    @ApiResponse(responseCode = "429", description = "Rate limit exceeded")
+            }
+    )
+    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<Void> receivePing(@RequestBody @Valid PingDTO reqDto, HttpServletRequest request) throws MetadataRepositoryException {
+        logger.info("Received ping from {}", utilityService.getRemoteAddr(request));
+        final Event event = eventService.acceptIncomingPing(reqDto, request);
+        logger.info("Triggering metadata retrieval for {}", event.getRelatedTo().getClientUrl());
+        eventService.triggerMetadataRetrieval(event);
+        harvesterService.harvest(reqDto.getClientUrl());
+        webhookService.triggerWebhooks(event);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexSettingsController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexSettingsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ad99c37c1f703de6cdc35ac5e60f6721c8fc4f4
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/index/IndexSettingsController.java
@@ -0,0 +1,61 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.index;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsUpdateDTO;
+import nl.dtls.fairdatapoint.service.index.settings.IndexSettingsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@Tag(name = "Index")
+@RestController
+@RequestMapping("/index/settings")
+public class IndexSettingsController {
+
+    @Autowired
+    private IndexSettingsService indexSettingsService;
+
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    @PreAuthorize("hasRole('ADMIN')")
+    public IndexSettingsDTO getIndexSettings() {
+        return indexSettingsService.getCurrentSettings();
+    }
+
+    @PutMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    @PreAuthorize("hasRole('ADMIN')")
+    public IndexSettingsDTO updateIndexSettings(@RequestBody @Valid IndexSettingsUpdateDTO reqDto) {
+        return indexSettingsService.updateSettings(reqDto);
+    }
+
+    @DeleteMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    @PreAuthorize("hasRole('ADMIN')")
+    public IndexSettingsDTO resetIndexSettings() {
+        return indexSettingsService.resetSettings();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/label/LabelController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/label/LabelController.java
new file mode 100644
index 0000000000000000000000000000000000000000..401c6a29fc7383d397b45f49422e747692f8de1f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/label/LabelController.java
@@ -0,0 +1,53 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.label;
+
+import nl.dtls.fairdatapoint.api.dto.label.LabelDTO;
+import nl.dtls.fairdatapoint.service.label.LabelService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/label")
+public class LabelController {
+    @Autowired
+    private LabelService labelService;
+
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<LabelDTO> getLabel(@RequestParam String iri,
+                                             @RequestParam(required = false, defaultValue = "en") String lang) {
+        var label = labelService.getLabel(iri, lang);
+
+        if (label.isPresent()) {
+            return new ResponseEntity<>(label.get(), HttpStatus.OK);
+        } else {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/membership/MembershipController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/membership/MembershipController.java
index aff73fbff9fc1c9a8eb0ea7f5d6c54b5ec343a40..2880173b37bd543617d5b47ad6ad23d098461c56 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/membership/MembershipController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/membership/MembershipController.java
@@ -22,17 +22,21 @@
  */
 package nl.dtls.fairdatapoint.api.controller.membership;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
 import nl.dtls.fairdatapoint.api.dto.membership.MembershipDTO;
 import nl.dtls.fairdatapoint.service.membership.MembershipService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
+@Tag(name = "Authentication and Authorization")
 @RestController
 @RequestMapping("/memberships")
 public class MembershipController {
@@ -40,7 +44,7 @@ public class MembershipController {
     @Autowired
     private MembershipService membershipService;
 
-    @RequestMapping(method = RequestMethod.GET)
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<List<MembershipDTO>> getUsers() {
         List<MembershipDTO> dto = membershipService.getMemberships();
         return new ResponseEntity<>(dto, HttpStatus.OK);
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericController.java
index feacd11b760ea4f81983fb0b0fbeff3537b76987..b54fd654201997c79666876c6b81dca8a4d652a8 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericController.java
@@ -22,32 +22,52 @@
  */
 package nl.dtls.fairdatapoint.api.controller.metadata;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepository;
+import nl.dtls.fairdatapoint.entity.exception.ForbiddenException;
 import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import nl.dtls.fairdatapoint.entity.user.User;
 import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
+import nl.dtls.fairdatapoint.service.metadata.enhance.MetadataEnhancer;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
 import nl.dtls.fairdatapoint.service.metadata.factory.MetadataServiceFactory;
+import nl.dtls.fairdatapoint.service.metadata.state.MetadataStateService;
+import nl.dtls.fairdatapoint.service.openapi.OpenApiService;
 import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
 import nl.dtls.fairdatapoint.service.shape.ShapeService;
+import nl.dtls.fairdatapoint.service.user.CurrentUserService;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Value;
 import org.eclipse.rdf4j.model.impl.LinkedHashModel;
 import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
 import org.eclipse.rdf4j.rio.RDFFormat;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.Optional;
 
+import static java.util.stream.Collectors.toList;
 import static nl.dtls.fairdatapoint.util.HttpUtil.*;
 import static nl.dtls.fairdatapoint.util.RdfIOUtil.changeBaseUri;
 import static nl.dtls.fairdatapoint.util.RdfIOUtil.read;
 import static nl.dtls.fairdatapoint.util.RdfUtil.*;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
+@Tag(name = "Metadata")
 @RestController
 @RequestMapping("/")
 public class GenericController {
@@ -65,42 +85,50 @@ public class GenericController {
     @Autowired
     private ShapeService shapeService;
 
-    @RequestMapping(
-            value = "**/spec",
-            method = RequestMethod.GET,
-            produces = {"!application/json"})
+    @Autowired
+    private MetadataStateService metadataStateService;
+
+    @Autowired
+    private MetadataEnhancer metadataEnhancer;
+
+    @Autowired
+    private CurrentUserService currentUserService;
+
+    @Autowired
+    private GenericMetadataRepository metadataRepository;
+
+    @Operation(hidden = true)
+    @GetMapping(path = "**/spec", produces = {"!application/json"})
     public Model getFormMetadata() {
         return shapeService.getShaclFromShapes();
     }
 
-    @RequestMapping(
-            value = "**/expanded",
-            method = RequestMethod.GET,
-            produces = {"!application/json"})
+    @Operation(hidden = true, deprecated = true)
+    @GetMapping(path = "**/expanded", produces = {"!application/json"})
     public Model getMetaDataExpanded(HttpServletRequest request) throws MetadataServiceException {
         // 1. Init
         String uri = getRequestURL(request, persistentUrl);
         Model resultRdf = new LinkedHashModel();
         String urlPrefix = getResourceNameForDetail(uri);
         MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
 
-        // 2. Get resource definition
-        ResourceDefinition rd = resourceDefinitionService.getByUriPrefix(urlPrefix);
-
-        // 3. Get entity
+        // 2. Get entity
         IRI entityUri = i(getRequestURL(request, persistentUrl));
         Model entity = metadataService.retrieve(entityUri);
         resultRdf.addAll(entity);
 
-        // 3. Get children
-        if (rd.getChild() != null) {
-            for (org.eclipse.rdf4j.model.Value datasetUri : getObjectsBy(entity, entityUri, i(rd.getChild()))) {
-                Model dataset = metadataService.retrieve(i(datasetUri.stringValue()));
-                resultRdf.addAll(dataset);
-            }
+        // 3. Check if it is draft
+        Metadata state = metadataStateService.get(entityUri);
+        Optional<User> oCurrentUser = currentUserService.getCurrentUser();
+        if (state.getState().equals(MetadataState.DRAFT) && oCurrentUser.isEmpty()) {
+            throw new ForbiddenException("You are not allow to view this record in state DRAFT");
         }
 
-        // 4. Get parent
+        // 4. Enhance
+        metadataEnhancer.enhanceWithResourceDefinition(entityUri, rd, resultRdf);
+
+        // 5. Get parent
         while (true) {
             IRI parentUri = i(getStringObjectBy(entity, entityUri, DCTERMS.IS_PART_OF));
             if (parentUri == null) {
@@ -116,10 +144,8 @@ public class GenericController {
         return resultRdf;
     }
 
-    @RequestMapping(
-            value = "**",
-            method = RequestMethod.GET,
-            produces = {"!application/json"})
+    @Operation(hidden = true)
+    @GetMapping(path = "**", produces = {"!application/json"})
     public Model getMetaData(HttpServletRequest request) throws MetadataServiceException {
         // 1. Init
         String uri = getRequestURL(request, persistentUrl);
@@ -127,53 +153,80 @@ public class GenericController {
         String urlPrefix = getResourceNameForDetail(uri);
         MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
 
-        // 2. Get entity
+        // 2. Get resource definition
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
+
+        // 3. Get entity
         IRI entityUri = i(getRequestURL(request, persistentUrl));
         Model entity = metadataService.retrieve(entityUri);
         resultRdf.addAll(entity);
 
-        // 3. Create response
+        // 4. Check if it is DRAFT
+        Metadata state = metadataStateService.get(entityUri);
+        Optional<User> oCurrentUser = currentUserService.getCurrentUser();
+        if (state.getState().equals(MetadataState.DRAFT) && oCurrentUser.isEmpty()) {
+            throw new ForbiddenException("You are not allow to view this record in state DRAFT");
+        }
+
+        // 5. Filter children
+        for (ResourceDefinitionChild rdChild : rd.getChildren()) {
+            IRI relationUri = i(rdChild.getRelationUri());
+            for (org.eclipse.rdf4j.model.Value childUri : getObjectsBy(entity, entityUri, relationUri)) {
+                Metadata childState = metadataStateService.get(i(childUri.stringValue()));
+                if (!(childState.getState().equals(MetadataState.PUBLISHED) || oCurrentUser.isPresent())) {
+                    resultRdf.remove(entityUri, relationUri, childUri);
+                }
+            }
+        }
+
+        // 6. Add links
+        metadataEnhancer.enhanceWithLinks(entityUri, entity, rd, persistentUrl, resultRdf);
+        metadataEnhancer.enhanceWithResourceDefinition(entityUri, rd, resultRdf);
+
+        // 7. Create response
         return resultRdf;
     }
 
-    @RequestMapping(
-            value = "**",
-            method = RequestMethod.POST,
-            produces = {"!application/json"})
+    @Operation(hidden = true)
+    @PostMapping(path = "**", produces = {"!application/json"})
     public ResponseEntity<Model> storeMetaData(HttpServletRequest request,
                                                @RequestBody String reqBody,
                                                @RequestHeader(value = "Content-Type", required = false) String contentType)
             throws MetadataServiceException {
-        // 1. Init
+        // 1. Check if user is authenticated
+        //     - it can't be in SecurityConfig because the authentication is done based on content-type
+        Optional<User> oUser = currentUserService.getCurrentUser();
+        if (oUser.isEmpty()) {
+            throw new ForbiddenException("You have to be login at first");
+        }
+
+        // 2. Init
         String urlPrefix = getResourceNameForList(getRequestURL(request, persistentUrl));
         MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
-        ResourceDefinition rd = resourceDefinitionService.getByUriPrefix(urlPrefix);
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
 
-        // 2. Generate URI
+        // 3. Generate URI
         IRI uri = generateNewIRI(request, persistentUrl);
 
-        // 3. Parse reqDto
+        // 4. Parse reqDto
         RDFFormat rdfContentType = getRdfContentType(contentType);
         Model oldDto = read(reqBody, uri.stringValue(), rdfContentType);
-        Model reqDto = changeBaseUri(oldDto, uri.stringValue(), rd.getShaclTargetClasses());
-        String child = rd.getChild();
-        if (child != null) {
-            reqDto.remove(null, i(child), null);
+        Model reqDto = changeBaseUri(oldDto, uri.stringValue(), resourceDefinitionService.getTargetClassUris(rd));
+        for (ResourceDefinitionChild rdChild : rd.getChildren()) {
+            reqDto.remove(null, i(rdChild.getRelationUri()), null);
         }
 
-        // 4. Store metadata
+        // 5. Store metadata
         Model metadata = metadataService.store(reqDto, uri, rd);
 
-        // 5. Create response
+        // 6. Create response
         return ResponseEntity
                 .created(URI.create(uri.stringValue()))
                 .body(metadata);
     }
 
-    @RequestMapping(
-            value = "**",
-            method = RequestMethod.PUT,
-            produces = {"!application/json"})
+    @Operation(hidden = true)
+    @PutMapping(path = "**", produces = {"!application/json"})
     public ResponseEntity<Model> updateMetaData(HttpServletRequest request,
                                                 @RequestBody String reqBody,
                                                 @RequestHeader(value = "Content-Type", required = false) String contentType)
@@ -181,7 +234,7 @@ public class GenericController {
         // 1. Init
         String urlPrefix = getResourceNameForDetail(getRequestURL(request, persistentUrl));
         MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
-        ResourceDefinition rd = resourceDefinitionService.getByUriPrefix(urlPrefix);
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
 
         // 2. Extract URI
         IRI uri = i(getRequestURL(request, persistentUrl));
@@ -189,9 +242,8 @@ public class GenericController {
         // 3. Parse reqDto
         RDFFormat rdfContentType = getRdfContentType(contentType);
         Model reqDto = read(reqBody, uri.stringValue(), rdfContentType);
-        String child = rd.getChild();
-        if (child != null) {
-            org.eclipse.rdf4j.model.Value childEntity = getObjectBy(reqDto, null, i(child));
+        for (ResourceDefinitionChild child : rd.getChildren()) {
+            org.eclipse.rdf4j.model.Value childEntity = getObjectBy(reqDto, null, i(child.getRelationUri()));
             if (childEntity != null) {
                 reqDto.remove(i(childEntity.stringValue()), null, null);
             }
@@ -205,12 +257,14 @@ public class GenericController {
                 .ok(metadata);
     }
 
-    @RequestMapping(value = "**", method = RequestMethod.DELETE)
+    @Operation(hidden = true)
+    @DeleteMapping(path = "**")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
     public ResponseEntity<Void> deleteMetadata(HttpServletRequest request) throws MetadataServiceException {
         // 1. Init
         String urlPrefix = getResourceNameForDetail(getRequestURL(request, persistentUrl));
         MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
-        ResourceDefinition rd = resourceDefinitionService.getByUriPrefix(urlPrefix);
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
 
         // 2. Skip if Repository (we don't support delete for repository)
         if (rd.getName().equals("Repository")) {
@@ -227,6 +281,77 @@ public class GenericController {
         return ResponseEntity.noContent().build();
     }
 
+    @Operation(hidden = true)
+    @GetMapping(path = "**/page/{childPrefix}", produces = {"!application/json"})
+    public ResponseEntity<Model> getMetaDataChildren(
+            @PathVariable final String childPrefix,
+            @RequestParam(defaultValue = "0") final int page,
+            @RequestParam(defaultValue = "10") final int size,
+            HttpServletRequest request
+    ) throws MetadataServiceException, MetadataRepositoryException {
+        // 1. Init
+        String requestUrl = getRequestURL(request, persistentUrl);
+        Model resultRdf = new LinkedHashModel();
+        String urlPrefix = getResourceNameForChild(requestUrl);
+        MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
+
+        // 2. Get entity
+        IRI entityUri = getEntityIriForPagination(requestUrl);
+        Model entity = metadataService.retrieve(entityUri);
+
+        // 3. Check if it is draft
+        Metadata state = metadataStateService.get(entityUri);
+        Optional<User> oCurrentUser = currentUserService.getCurrentUser();
+        if (state.getState().equals(MetadataState.DRAFT) && oCurrentUser.isEmpty()) {
+            throw new ForbiddenException("You are not allow to view this record in state DRAFT");
+        }
+
+        // 4. Get Children
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
+        ResourceDefinition currentChildRd = resourceDefinitionService.getByUrlPrefix(childPrefix);
+        MetadataService childMetadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(childPrefix);
+
+        for (ResourceDefinitionChild rdChild : rd.getChildren()) {
+            if (rdChild.getResourceDefinitionUuid().equals(currentChildRd.getUuid())) {
+                IRI relationUri = i(rdChild.getRelationUri());
+
+                // 4.1 Get all titles for sort
+                var titles = metadataRepository.findChildTitles(entityUri, relationUri);
+
+                // 4.2 Get all children sorted
+                var children = getObjectsBy(entity, entityUri, relationUri)
+                        .stream()
+                        .filter((childUri) -> getResourceNameForChild(childUri.toString()).equals(childPrefix))
+                        .filter((childUri) -> {
+                            if (oCurrentUser.isPresent()) return true;
+                            Metadata childState = metadataStateService.get(i(childUri.stringValue()));
+                            return childState.getState().equals(MetadataState.PUBLISHED);
+                        })
+                        .sorted((v1, v2) -> {
+                            var title1 = titles.get(v1.toString());
+                            var title2 = titles.get(v2.toString());
+                            return title1.compareTo(title2);
+                        })
+                        .collect(toList());
+
+                // 4.3 Retrieve children metadata only for requested page
+                var childrenCount = children.size();
+                children.stream().skip(page * size).limit(size)
+                        .map((childUri) -> retrieveChildModel(childMetadataService, childUri))
+                        .flatMap(Optional::stream)
+                        .forEach(resultRdf::addAll);
+
+                // 4.4 Set Link headers and send response
+                HttpHeaders responseHeaders = new HttpHeaders();
+                responseHeaders.set("Link", createLinkHeader(requestUrl, childrenCount, page, size));
+                return ResponseEntity.ok().headers(responseHeaders).body(resultRdf);
+            }
+        }
+
+        // Send empty response in case nothing was found
+        return ResponseEntity.ok(resultRdf);
+    }
+
     private String getResourceNameForList(String url) {
         url = url.replace(persistentUrl, "");
 
@@ -252,4 +377,61 @@ public class GenericController {
         return parts[1];
     }
 
+    private String getResourceNameForChild(String url) {
+        url = url.replace(persistentUrl, "");
+        String[] parts = url.split("/");
+
+        if (parts.length < 2) {
+            throw new ValidationException("Unsupported URL");
+        }
+
+        // If URL is a repository -> return empty string
+        if (parts[1].equals("page")) {
+            return "";
+        }
+
+        return parts[1];
+    }
+
+    private IRI getEntityIriForPagination(String url) {
+        String[] parts = url.split("/");
+
+        StringBuilder sb = new StringBuilder(parts[0]);
+        for (int i = 1; i < parts.length - 2; i++) {
+            sb.append("/");
+            sb.append(parts[i]);
+        }
+        return i(sb.toString());
+    }
+
+    private String createLinkHeader(String requestUrl, int childrenCount, int page, int size) {
+        var links = new LinkedList<String>();
+        var lastPage = (int) Math.ceil((float) childrenCount / size) - 1;
+
+        links.add(createLink(requestUrl, 0, size, "first"));
+        links.add(createLink(requestUrl, lastPage, size, "last"));
+
+        if (page > 0 && page <= lastPage) {
+            links.add(createLink(requestUrl, page - 1, size, "prev"));
+        }
+
+        if (page < lastPage && page >= 0) {
+            links.add(createLink(requestUrl, page + 1, size, "next"));
+        }
+
+        return String.join(", ", links);
+    }
+
+    private Optional<Model> retrieveChildModel(MetadataService childMetadataService, Value childUri) {
+        try {
+            Model childModel = childMetadataService.retrieve(i(childUri.stringValue()));
+            return Optional.of(childModel);
+        } catch (MetadataServiceException e) {
+            return Optional.empty();
+        }
+    }
+
+    private String createLink(String requestUrl, int page, int size, String rel) {
+        return "<" + requestUrl + "/?page=" + page + "&size=" + size + ">; rel=\"" + rel + "\"";
+    }
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMemberController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMemberController.java
index 4d3895d34d2f6585159f425327407281ecee8d0d..0d6edb2893c6557ee1abbd39c5fa1dc2715af5b2 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMemberController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMemberController.java
@@ -22,6 +22,8 @@
  */
 package nl.dtls.fairdatapoint.api.controller.metadata;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import nl.dtls.fairdatapoint.api.dto.member.MemberCreateDTO;
 import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
 import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
@@ -35,19 +37,20 @@ import org.eclipse.rdf4j.model.Model;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import java.util.List;
-import java.util.Optional;
 
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getMetadataIdentifier;
 import static nl.dtls.fairdatapoint.util.HttpUtil.getRequestURL;
 import static nl.dtls.fairdatapoint.util.RdfUtil.removeLastPartOfIRI;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
+@Tag(name = "Authentication and Authorization")
 @RestController
 public class GenericMemberController {
 
@@ -61,24 +64,8 @@ public class GenericMemberController {
     @Autowired
     private MetadataServiceFactory metadataServiceFactory;
 
-    @RequestMapping(value = "**/member", method = RequestMethod.GET)
-    public MemberDTO getMember(HttpServletRequest request) throws MetadataServiceException {
-        // 1. Init
-        String urlPrefix = getResourceNameForList(getRequestURL(request, persistentUrl));
-        MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
-
-        // 2. Get and check existence entity
-        IRI uri = i(getRequestURL(request, persistentUrl));
-        IRI entityUri = removeLastPartOfIRI(uri);
-        Model metadata = metadataService.retrieve(entityUri);
-
-        // 3. Get member
-        String entityId = getMetadataIdentifier(metadata).getIdentifier().getLabel();
-        Optional<MemberDTO> oMember = memberService.getMemberForCurrentUser(entityId, Metadata.class);
-        return oMember.orElse(new MemberDTO(null, null));
-    }
-
-    @RequestMapping(value = "**/members", method = RequestMethod.GET)
+    @Operation(hidden = true)
+    @GetMapping(path = "**/members", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<List<MemberDTO>> getMembers(HttpServletRequest request)
             throws ResourceNotFoundException, MetadataServiceException {
         // 1. Init
@@ -96,7 +83,8 @@ public class GenericMemberController {
         return new ResponseEntity<>(dto, HttpStatus.OK);
     }
 
-    @RequestMapping(value = "**/members/{userUuid}", method = RequestMethod.PUT)
+    @Operation(hidden = true)
+    @PutMapping(path = "**/members/{userUuid}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<MemberDTO> putMember(@PathVariable final String userUuid,
                                                HttpServletRequest request,
                                                @RequestBody @Valid MemberCreateDTO reqBody)
@@ -117,7 +105,9 @@ public class GenericMemberController {
         return new ResponseEntity<>(dto, HttpStatus.OK);
     }
 
-    @RequestMapping(value = "**/members/{userUuid}", method = RequestMethod.DELETE)
+    @Operation(hidden = true)
+    @DeleteMapping(path = "**/members/{userUuid}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
     public ResponseEntity<Void> deleteMember(@PathVariable final String userUuid, HttpServletRequest request)
             throws ResourceNotFoundException, MetadataServiceException {
         // 1. Init
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMetaController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMetaController.java
new file mode 100644
index 0000000000000000000000000000000000000000..64ffa7cf6cc65385165685cf248c00a86f1249fa
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/metadata/GenericMetaController.java
@@ -0,0 +1,159 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.metadata;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaPathDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.member.MemberService;
+import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
+import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
+import nl.dtls.fairdatapoint.service.metadata.factory.MetadataServiceFactory;
+import nl.dtls.fairdatapoint.service.metadata.state.MetadataStateService;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.Optional;
+
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getMetadataIdentifier;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getTitle;
+import static nl.dtls.fairdatapoint.util.HttpUtil.getRequestURL;
+import static nl.dtls.fairdatapoint.util.RdfUtil.getStringObjectBy;
+import static nl.dtls.fairdatapoint.util.RdfUtil.removeLastPartOfIRI;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+@Tag(name = "Metadata")
+@RestController
+public class GenericMetaController {
+
+    @Autowired
+    @Qualifier("persistentUrl")
+    private String persistentUrl;
+
+    @Autowired
+    private MemberService memberService;
+
+    @Autowired
+    private MetadataServiceFactory metadataServiceFactory;
+
+    @Autowired
+    private MetadataStateService metadataStateService;
+
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
+
+    @Operation(hidden = true)
+    @RequestMapping(path = "**/meta", method = RequestMethod.GET)
+    public MetaDTO getMeta(HttpServletRequest request) throws MetadataServiceException {
+        // 1. Init
+        String urlPrefix = getResourceNameForList(getRequestURL(request, persistentUrl));
+        MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
+
+        // 2. Get resource definition
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
+
+        // 3. Get and check existence entity
+        IRI uri = i(getRequestURL(request, persistentUrl));
+        IRI entityUri = removeLastPartOfIRI(uri);
+        Model entity = metadataService.retrieve(entityUri);
+
+        // 4. Get member
+        String entityId = getMetadataIdentifier(entity).getIdentifier().getLabel();
+        Optional<MemberDTO> oMember = memberService.getMemberForCurrentUser(entityId, Metadata.class);
+        MemberDTO member = oMember.orElse(new MemberDTO(null, null));
+
+        // 5. Get state
+        MetaStateDTO state = metadataStateService.getState(entityUri, entity, rd);
+
+        // 6. Make path map
+        HashMap<String, MetaPathDTO> pathMap = new HashMap<>();
+        while (true) {
+            MetaPathDTO entry = new MetaPathDTO();
+            entry.setResourceDefinitionUuid(rd.getUuid());
+            entry.setTitle(getTitle(entity).stringValue());
+            IRI parentUri = i(getStringObjectBy(entity, entityUri, DCTERMS.IS_PART_OF));
+            Optional.ofNullable(parentUri).map(IRI::toString).ifPresent(entry::setParent);
+            pathMap.put(entityUri.toString(), entry);
+            if (parentUri == null) {
+                break;
+            }
+            entity = metadataService.retrieve(parentUri);
+            entityUri = parentUri;
+            urlPrefix = getResourceNameForList(parentUri.toString());
+            rd = resourceDefinitionService.getByUrlPrefix(urlPrefix);
+        }
+
+        return new MetaDTO(member, state, pathMap);
+    }
+
+    @Operation(hidden = true)
+    @RequestMapping(path = "**/meta/state", method = RequestMethod.PUT)
+    public MetaStateChangeDTO putMetaState(HttpServletRequest request, @RequestBody @Valid MetaStateChangeDTO reqDto) throws MetadataServiceException {
+        // 1. Init
+        String urlPrefix = getResourceNameForList(getRequestURL(request, persistentUrl));
+        MetadataService metadataService = metadataServiceFactory.getMetadataServiceByUrlPrefix(urlPrefix);
+
+        // 2. Get and check existence entity
+        IRI uri = i(getRequestURL(request, persistentUrl));
+        IRI entityUri = removeLastPartOfIRI(removeLastPartOfIRI(uri));
+        Model model = metadataService.retrieve(entityUri);
+
+        // 3. Get state
+        metadataStateService.modifyState(entityUri, reqDto);
+
+        return reqDto;
+    }
+
+    private String getResourceNameForList(String url) throws MetadataServiceException {
+        url = url.replace(persistentUrl, "")
+                .replace("/meta", "")
+                .replace("/state", "");
+
+        String[] parts = url.split("/");
+        if (parts.length == 1) {
+            return "";
+        }
+
+        if (parts.length != 3) {
+            throw new MetadataServiceException("Unsupported URL");
+        }
+        return parts[1];
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/profile/ProfileController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/profile/ProfileController.java
new file mode 100644
index 0000000000000000000000000000000000000000..970871a9becb97eec067b8b27724698ee74be67f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/profile/ProfileController.java
@@ -0,0 +1,77 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.profile;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.config.ConverterConfig;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.service.profile.ProfileService;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.util.HttpUtil.getRequestURL;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+@Tag(name = "Client")
+@RestController
+@RequestMapping("/profile")
+public class ProfileController {
+
+    @Autowired
+    @Qualifier("persistentUrl")
+    private String persistentUrl;
+
+    @Autowired
+    private ProfileService profileService;
+
+    @GetMapping(path = "/{uuid}", produces = {
+            "text/turtle",
+            "application/x-turtle",
+            "text/n3",
+            "text/rdf+n3",
+            "application/ld+json",
+            "application/rdf+xml",
+            "application/xml",
+            "text/xml",
+    })
+    public ResponseEntity<Model> getShapeContent(HttpServletRequest request, @PathVariable final String uuid)
+            throws ResourceNotFoundException {
+        IRI uri = i(getRequestURL(request, persistentUrl));
+        Optional<Model> oDto = profileService.getProfileByUuid(uuid, uri);
+        if (oDto.isPresent()) {
+            return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
+        } else {
+            throw new ResourceNotFoundException(format("Profile '%s' doesn't exist", uuid));
+        }
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/reset/ResetController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/reset/ResetController.java
new file mode 100644
index 0000000000000000000000000000000000000000..e92e226d6eb4bdc36b5f81fd20695e2298380097
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/reset/ResetController.java
@@ -0,0 +1,54 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.reset;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.reset.ResetDTO;
+import nl.dtls.fairdatapoint.service.reset.ResetService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+
+@Tag(name = "Client")
+@RestController
+public class ResetController {
+
+    @Autowired
+    private ResetService resetService;
+
+    @PostMapping("/reset")
+    @PreAuthorize("hasRole('ADMIN')")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<Void> postResetFactoryDefaults(@RequestBody @Valid ResetDTO reqDto) throws Exception {
+        resetService.resetToFactoryDefaults(reqDto);
+        return ResponseEntity.noContent().build();
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/resource/ResourceDefinitionController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/resource/ResourceDefinitionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..74748d4a529d469e914067cc39165bc743f370ae
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/resource/ResourceDefinitionController.java
@@ -0,0 +1,97 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.resource;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionChangeDTO;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionDTO;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.BindException;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Optional;
+
+import static java.lang.String.format;
+
+@Tag(name = "Metadata Model")
+@RestController
+@RequestMapping("/resource-definitions")
+public class ResourceDefinitionController {
+
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
+
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<ResourceDefinitionDTO>> getResourceDefinitions() {
+        List<ResourceDefinitionDTO> dto = resourceDefinitionService.getAll();
+        return new ResponseEntity<>(dto, HttpStatus.OK);
+    }
+
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<ResourceDefinitionDTO> createResourceDefinitions(@RequestBody @Valid ResourceDefinitionChangeDTO reqDto) throws BindException {
+        ResourceDefinitionDTO dto = resourceDefinitionService.create(reqDto);
+        return new ResponseEntity<>(dto, HttpStatus.OK);
+    }
+
+    @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<ResourceDefinitionDTO> getResourceDefinition(@PathVariable final String uuid)
+            throws ResourceNotFoundException {
+        Optional<ResourceDefinitionDTO> oDto = resourceDefinitionService.getDTOByUuid(uuid);
+        if (oDto.isPresent()) {
+            return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
+        } else {
+            throw new ResourceNotFoundException(format("Resource Definition '%s' doesn't exist", uuid));
+        }
+    }
+
+    @PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<ResourceDefinitionDTO> putResourceDefinitions(@PathVariable final String uuid,
+                                                                     @RequestBody @Valid ResourceDefinitionChangeDTO reqDto)
+            throws ResourceNotFoundException, BindException {
+        Optional<ResourceDefinitionDTO> oDto = resourceDefinitionService.update(uuid, reqDto);
+        if (oDto.isPresent()) {
+            return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
+        } else {
+            throw new ResourceNotFoundException(format("Resource Definition '%s' doesn't exist", uuid));
+        }
+    }
+
+    @DeleteMapping(path = "/{uuid}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<Void> deleteResourceDefinitions(@PathVariable final String uuid)
+            throws ResourceNotFoundException {
+        boolean result = resourceDefinitionService.deleteByUuid(uuid);
+        if (result) {
+            return ResponseEntity.noContent().build();
+        } else {
+            throw new ResourceNotFoundException(format("Resource Definition '%s' doesn't exist", uuid));
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/search/SearchController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/search/SearchController.java
new file mode 100644
index 0000000000000000000000000000000000000000..78f33f44595a251d6d469a94295edb409174c2ac
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/search/SearchController.java
@@ -0,0 +1,54 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.search;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.search.SearchQueryDTO;
+import nl.dtls.fairdatapoint.api.dto.search.SearchResultDTO;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.service.search.SearchService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@Tag(name = "Metadata")
+@RestController
+@RequestMapping("/search")
+public class SearchController {
+
+    @Autowired
+    private SearchService searchService;
+
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<SearchResultDTO>> search(@RequestBody @Valid SearchQueryDTO reqDto) throws MetadataRepositoryException {
+        return ResponseEntity.ok(searchService.search(reqDto));
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/settings/SettingsController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/settings/SettingsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5a93864218a5806c672027623dab05ac8ea7f27
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/settings/SettingsController.java
@@ -0,0 +1,61 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.controller.settings;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsUpdateDTO;
+import nl.dtls.fairdatapoint.service.settings.SettingsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@Tag(name = "Client")
+@RestController
+@RequestMapping("/settings")
+public class SettingsController {
+
+    @Autowired
+    private SettingsService settingsService;
+
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    @PreAuthorize("hasRole('ADMIN')")
+    public SettingsDTO getSettings() {
+        return settingsService.getCurrentSettings();
+    }
+
+    @PutMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    @PreAuthorize("hasRole('ADMIN')")
+    public SettingsDTO updateSettings(@RequestBody @Valid SettingsUpdateDTO reqDto) {
+        return settingsService.updateSettings(reqDto);
+    }
+
+    @DeleteMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    @PreAuthorize("hasRole('ADMIN')")
+    public SettingsDTO resetSettings() {
+        return settingsService.resetSettings();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/shape/ShapeController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/shape/ShapeController.java
index 078fbcca6ac5c36513a5a2d494c6f17aaa3bcbbf..bd9a2912c1cd4eb5515d0a6bc4336173a3bd8018 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/shape/ShapeController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/shape/ShapeController.java
@@ -22,13 +22,18 @@
  */
 package nl.dtls.fairdatapoint.api.controller.shape;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
 import nl.dtls.fairdatapoint.api.dto.shape.ShapeChangeDTO;
 import nl.dtls.fairdatapoint.api.dto.shape.ShapeDTO;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeRemoteDTO;
 import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
 import nl.dtls.fairdatapoint.service.shape.ShapeService;
+import org.eclipse.rdf4j.model.Model;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
@@ -37,6 +42,7 @@ import java.util.Optional;
 
 import static java.lang.String.format;
 
+@Tag(name = "Metadata Model")
 @RestController
 @RequestMapping("/shapes")
 public class ShapeController {
@@ -44,19 +50,40 @@ public class ShapeController {
     @Autowired
     private ShapeService shapeService;
 
-    @RequestMapping(method = RequestMethod.GET)
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<List<ShapeDTO>> getShapes() {
         List<ShapeDTO> dto = shapeService.getShapes();
         return new ResponseEntity<>(dto, HttpStatus.OK);
     }
 
-    @RequestMapping(method = RequestMethod.POST)
+    @GetMapping(path = "/public", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<ShapeDTO>> getPublishedShapes() {
+        List<ShapeDTO> dto = shapeService.getPublishedShapes();
+        return new ResponseEntity<>(dto, HttpStatus.OK);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @GetMapping(path = "/import", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<ShapeRemoteDTO>> getImportableShapes(@RequestParam(name = "from") String fdpUrl) {
+        List<ShapeRemoteDTO> dto = shapeService.getRemoteShapes(fdpUrl);
+        return new ResponseEntity<>(dto, HttpStatus.OK);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping(path = "/import", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<ShapeDTO>> importShapes(@RequestBody @Valid List<ShapeRemoteDTO> reqDtos) {
+        List<ShapeDTO> dto = shapeService.importShapes(reqDtos);
+        return new ResponseEntity<>(dto, HttpStatus.OK);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<ShapeDTO> createShape(@RequestBody @Valid ShapeChangeDTO reqDto) {
         ShapeDTO dto = shapeService.createShape(reqDto);
         return new ResponseEntity<>(dto, HttpStatus.OK);
     }
 
-    @RequestMapping(value = "/{uuid}", method = RequestMethod.GET)
+    @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<ShapeDTO> getShape(@PathVariable final String uuid)
             throws ResourceNotFoundException {
         Optional<ShapeDTO> oDto = shapeService.getShapeByUuid(uuid);
@@ -67,7 +94,28 @@ public class ShapeController {
         }
     }
 
-    @RequestMapping(value = "/{uuid}", method = RequestMethod.PUT)
+    @GetMapping(path = "/{uuid}", produces = {
+            "text/turtle",
+            "application/x-turtle",
+            "text/n3",
+            "text/rdf+n3",
+            "application/ld+json",
+            "application/rdf+xml",
+            "application/xml",
+            "text/xml",
+    })
+    public ResponseEntity<Model> getShapeContent(@PathVariable final String uuid)
+            throws ResourceNotFoundException {
+        Optional<Model> oDto = shapeService.getShapeContentByUuid(uuid);
+        if (oDto.isPresent()) {
+            return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
+        } else {
+            throw new ResourceNotFoundException(format("Shape '%s' doesn't exist", uuid));
+        }
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<ShapeDTO> putShape(@PathVariable final String uuid,
                                              @RequestBody @Valid ShapeChangeDTO reqDto) throws ResourceNotFoundException {
         Optional<ShapeDTO> oDto = shapeService.updateShape(uuid, reqDto);
@@ -78,7 +126,9 @@ public class ShapeController {
         }
     }
 
-    @RequestMapping(value = "/{uuid}", method = RequestMethod.DELETE)
+    @PreAuthorize("hasRole('ADMIN')")
+    @DeleteMapping("/{uuid}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
     public ResponseEntity<Void> deleteShape(@PathVariable final String uuid)
             throws ResourceNotFoundException {
         boolean result = shapeService.deleteShape(uuid);
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/token/TokenController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/token/TokenController.java
index 9d5109015ea6b61a3311e5b00d1f6f247f6bb779..b6344729ad86b368f67d9b17a53617f1821d83bb 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/token/TokenController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/token/TokenController.java
@@ -22,22 +22,22 @@
  */
 package nl.dtls.fairdatapoint.api.controller.token;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
 import nl.dtls.fairdatapoint.api.dto.auth.AuthDTO;
 import nl.dtls.fairdatapoint.api.dto.auth.TokenDTO;
 import nl.dtls.fairdatapoint.service.jwt.JwtService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.core.AuthenticationException;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 
+@Tag(name = "Authentication and Authorization")
 @RestController
 @RequestMapping("/tokens")
 public class TokenController {
@@ -45,7 +45,7 @@ public class TokenController {
     @Autowired
     private JwtService jwtService;
 
-    @PostMapping
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<TokenDTO> generateToken(@RequestBody @Valid AuthDTO reqDto) {
         try {
             String token = jwtService.createToken(reqDto);
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/controller/user/UserController.java b/src/main/java/nl/dtls/fairdatapoint/api/controller/user/UserController.java
index 56e0615985fb921ee673b2fc877df0e8aaca1489..b84c4c29735f76881694a0d71a8003a41ccc5068 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/controller/user/UserController.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/controller/user/UserController.java
@@ -22,16 +22,15 @@
  */
 package nl.dtls.fairdatapoint.api.controller.user;
 
-import nl.dtls.fairdatapoint.api.dto.user.UserChangeDTO;
-import nl.dtls.fairdatapoint.api.dto.user.UserCreateDTO;
-import nl.dtls.fairdatapoint.api.dto.user.UserDTO;
-import nl.dtls.fairdatapoint.api.dto.user.UserPasswordDTO;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.user.*;
 import nl.dtls.fairdatapoint.entity.exception.ForbiddenException;
 import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
 import nl.dtls.fairdatapoint.service.user.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
@@ -41,6 +40,7 @@ import java.util.Optional;
 
 import static java.lang.String.format;
 
+@Tag(name = "User Management")
 @RestController
 @RequestMapping("/users")
 public class UserController {
@@ -48,21 +48,21 @@ public class UserController {
     @Autowired
     private UserService userService;
 
-    @RequestMapping(method = RequestMethod.GET)
+    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<List<UserDTO>> getUsers() {
         List<UserDTO> dto = userService.getUsers();
         return new ResponseEntity<>(dto, HttpStatus.OK);
     }
 
-    @RequestMapping(method = RequestMethod.POST)
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<UserDTO> createUser(@RequestBody @Valid UserCreateDTO reqDto) {
         UserDTO dto = userService.createUser(reqDto);
         return new ResponseEntity<>(dto, HttpStatus.OK);
     }
 
-    @RequestMapping(value = "/current", method = RequestMethod.GET)
-    public ResponseEntity<UserDTO> getUserCurrent()
-            throws ResourceNotFoundException {
+    @Tag(name = "Authentication and Authorization")
+    @GetMapping(path = "/current", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<UserDTO> getUserCurrent() throws ResourceNotFoundException {
         Optional<UserDTO> oDto = userService.getCurrentUser();
         if (oDto.isPresent()) {
             return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
@@ -71,9 +71,8 @@ public class UserController {
         }
     }
 
-    @RequestMapping(value = "/{uuid}", method = RequestMethod.GET)
-    public ResponseEntity<UserDTO> getUser(@PathVariable final String uuid)
-            throws ResourceNotFoundException {
+    @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<UserDTO> getUser(@PathVariable final String uuid) throws ResourceNotFoundException {
         Optional<UserDTO> oDto = userService.getUserByUuid(uuid);
         if (oDto.isPresent()) {
             return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
@@ -82,7 +81,17 @@ public class UserController {
         }
     }
 
-    @RequestMapping(value = "/{uuid}", method = RequestMethod.PUT)
+    @PutMapping(path = "/current", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<UserDTO> putUserCurrent(@RequestBody @Valid UserProfileChangeDTO reqDto) throws ResourceNotFoundException {
+        Optional<UserDTO> oDto = userService.updateCurrentUser(reqDto);
+        if (oDto.isPresent()) {
+            return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
+        } else {
+            throw new ForbiddenException("You have to be login at first");
+        }
+    }
+
+    @PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<UserDTO> putUser(@PathVariable final String uuid,
                                            @RequestBody @Valid UserChangeDTO reqDto) throws ResourceNotFoundException {
         Optional<UserDTO> oDto = userService.updateUser(uuid, reqDto);
@@ -93,7 +102,18 @@ public class UserController {
         }
     }
 
-    @RequestMapping(value = "/{uuid}/password", method = RequestMethod.PUT)
+    @PutMapping(path = "/current/password", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<UserDTO> putUserCurrentPassword(@RequestBody @Valid UserPasswordDTO reqDto)
+            throws ResourceNotFoundException {
+        Optional<UserDTO> oDto = userService.updatePasswordForCurrentUser(reqDto);
+        if (oDto.isPresent()) {
+            return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
+        } else {
+            throw new ForbiddenException("You have to be login at first");
+        }
+    }
+
+    @PutMapping(path = "/{uuid}/password", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<UserDTO> putUserPassword(@PathVariable final String uuid,
                                                    @RequestBody @Valid UserPasswordDTO reqDto) throws ResourceNotFoundException {
         Optional<UserDTO> oDto = userService.updatePassword(uuid, reqDto);
@@ -104,8 +124,9 @@ public class UserController {
         }
     }
 
-    @RequestMapping(value = "/{uuid}", method = RequestMethod.DELETE)
-    public HttpEntity deleteUser(@PathVariable final String uuid)
+    @DeleteMapping("/{uuid}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<Void> deleteUser(@PathVariable final String uuid)
             throws ResourceNotFoundException {
         boolean result = userService.deleteUser(uuid);
         if (result) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/converter/RdfConverter.java b/src/main/java/nl/dtls/fairdatapoint/api/converter/RdfConverter.java
index 9e497d4e834d0ced608ebd0d346fa250741f2fc5..59715f8e1f07b9dd18b71963c96bda520e928df9 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/converter/RdfConverter.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/converter/RdfConverter.java
@@ -43,7 +43,7 @@ import java.io.IOException;
 
 public class RdfConverter extends AbstractHttpMessageConverter<Model> {
 
-    private RDFFormat format;
+    private final RDFFormat format;
 
     public RdfConverter(RDFFormat format) {
         super(getMediaTypes(format));
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/apikey/ApiKeyDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/apikey/ApiKeyDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..171c09f78146a68420718389bfbfd9698a20b073
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/apikey/ApiKeyDTO.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.apikey;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class ApiKeyDTO {
+
+    private String uuid;
+
+    private String token;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/config/BootstrapConfigDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/config/BootstrapConfigDTO.java
index 5e7b264633aceb7710d353ed12d47b85cc3a24d0..1d23539071ff4bdcf8018b50255d62517cc17cf7 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/dto/config/BootstrapConfigDTO.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/config/BootstrapConfigDTO.java
@@ -26,6 +26,10 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionDTO;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+
+import java.util.List;
 
 @NoArgsConstructor
 @AllArgsConstructor
@@ -35,4 +39,8 @@ public class BootstrapConfigDTO {
 
     protected String persistentUrl;
 
+    protected List<ResourceDefinitionDTO> resourceDefinitions;
+
+    protected boolean index;
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/dashboard/DashboardItemDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/dashboard/DashboardItemDTO.java
index 08a4812f9e0da75e1af3c8d1938da9ce0333ee6c..d9f5b51cce57d1a75d0279ff92db355346914af3 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/dto/dashboard/DashboardItemDTO.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/dashboard/DashboardItemDTO.java
@@ -27,6 +27,7 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import nl.dtls.fairdatapoint.api.dto.membership.MembershipDTO;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
 
 import java.util.List;
 import java.util.Optional;
@@ -45,4 +46,6 @@ public class DashboardItemDTO {
 
     protected Optional<MembershipDTO> membership;
 
+    protected MetadataState state;
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..b891d7d27588ef997870ebe4cea96cff366467c4
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryDTO.java
@@ -0,0 +1,54 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.entry;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class IndexEntryDTO {
+
+    @NotNull
+    private String uuid;
+
+    @NotNull
+    @URL
+    private String clientUrl;
+
+    @NotNull
+    private IndexEntryStateDTO state;
+
+    @NotNull
+    private String registrationTime;
+
+    @NotNull
+    private String modificationTime;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryDetailDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryDetailDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..28583103eef94cf77fa7e7c7c3bf9697aceafc80
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryDetailDTO.java
@@ -0,0 +1,67 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.entry;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.api.dto.index.event.EventDTO;
+import nl.dtls.fairdatapoint.entity.index.entry.RepositoryMetadata;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class IndexEntryDetailDTO {
+
+    @NotNull
+    private String uuid;
+
+    @NotNull
+    @URL
+    private String clientUrl;
+
+    @NotNull
+    private IndexEntryStateDTO state;
+
+    @NotNull
+    private RepositoryMetadata currentMetadata;
+
+    @NotNull
+    private List<EventDTO> events;
+
+    @NotNull
+    private String registrationTime;
+
+    @NotNull
+    private String modificationTime;
+
+    @NotNull
+    private String lastRetrievalTime;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryInfoDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryInfoDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..0799270ef039c5c6a5bf648eee3e4149ab93c384
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryInfoDTO.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.entry;
+
+import lombok.*;
+
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class IndexEntryInfoDTO {
+
+    private Map<String, Long> entriesCount;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryStateDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryStateDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..6890aea466910636ddb29c456993742ad9fa2949
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/entry/IndexEntryStateDTO.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.entry;
+
+public enum IndexEntryStateDTO {
+
+    UNKNOWN,
+
+    ACTIVE,
+
+    INACTIVE,
+
+    UNREACHABLE,
+
+    INVALID
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/event/EventDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/event/EventDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bae018af97d2fddce60efddd94cbf692fa47a41
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/event/EventDTO.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.entity.index.event.EventType;
+
+import javax.validation.constraints.NotNull;
+import java.util.UUID;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class EventDTO {
+
+    @NotNull
+    private UUID uuid;
+
+    @NotNull
+    private EventType type;
+
+    @NotNull
+    private String created;
+
+    private String finished;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/ping/PingDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/ping/PingDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..c14d82126ec320cb47340a6daffe27b6805a9c65
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/ping/PingDTO.java
@@ -0,0 +1,43 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.ping;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class PingDTO {
+
+    @NotNull
+    @URL
+    private String clientUrl;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..448d4969d894b9ce3e7cb08bcc675fa975782629
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsDTO.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class IndexSettingsDTO {
+    @NotNull
+    private IndexSettingsRetrievalDTO retrieval;
+
+    @NotNull
+    private IndexSettingsPingDTO ping;
+
+    @NotNull
+    private Boolean isDefault;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsPingDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsPingDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..616e6826ccf4fe3570f7cbc9f57149d1feddc7d6
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsPingDTO.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.api.validator.ValidDuration;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class IndexSettingsPingDTO {
+    @NotNull
+    @ValidDuration
+    private String validDuration;
+    @NotNull
+    @ValidDuration
+    private String rateLimitDuration;
+    @NotNull
+    private Integer rateLimitHits;
+    @NotNull
+    private List<String> denyList;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsRetrievalDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsRetrievalDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..d630da3432a47b8a0a8a3122a296dc8df8277802
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsRetrievalDTO.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.api.validator.ValidDuration;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class IndexSettingsRetrievalDTO {
+    @NotNull
+    @ValidDuration
+    private String rateLimitWait;
+
+    @NotNull
+    @ValidDuration
+    private String timeout;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsUpdateDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsUpdateDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..09905c30fb62bc739bded419b1a7865d16f66487
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/settings/IndexSettingsUpdateDTO.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class IndexSettingsUpdateDTO {
+    @Valid
+    @NotNull
+    private IndexSettingsRetrievalDTO retrieval;
+
+    @Valid
+    @NotNull
+    private IndexSettingsPingDTO ping;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/index/webhook/WebhookPayloadDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/webhook/WebhookPayloadDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd2a682000e8ab288cfa164bf40987415bb469c5
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/index/webhook/WebhookPayloadDTO.java
@@ -0,0 +1,37 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.index.webhook;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.dtls.fairdatapoint.entity.index.webhook.WebhookEvent;
+
+@Data
+@NoArgsConstructor
+public class WebhookPayloadDTO {
+    private WebhookEvent event;
+    private String uuid;
+    private String clientUrl;
+    private String timestamp;
+    private String secret;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/label/LabelDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/label/LabelDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..58290dc1a190e0cefffb7618d32dbd2ac2a22b7a
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/label/LabelDTO.java
@@ -0,0 +1,35 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.label;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class LabelDTO {
+    private String label;
+    private String lang;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/membership/MembershipDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/membership/MembershipDTO.java
index c6e293be1eb6c451e60f2cc7c6c62fdb89c99c5a..69798d0d5e7422fcf8d3587c4da494dd834d1aad 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/dto/membership/MembershipDTO.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/membership/MembershipDTO.java
@@ -24,7 +24,6 @@ package nl.dtls.fairdatapoint.api.dto.membership;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
-import nl.dtls.fairdatapoint.entity.membership.MembershipEntity;
 
 import java.util.List;
 
@@ -38,6 +37,6 @@ public class MembershipDTO {
 
     private List<MembershipPermissionDTO> permissions;
 
-    private List<MembershipEntity> allowedEntities;
+    private List<String> allowedEntities;
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa6598ccaa550da14b0b53b8c6aa6bbdad4db4cb
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaDTO.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.metadata;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@Schema(name = "MetaDTO")
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class MetaDTO {
+
+    @JsonInclude
+    private MemberDTO member;
+
+    @JsonInclude
+    private MetaStateDTO state;
+
+    @NotNull
+    private Map<String, MetaPathDTO> path;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaPathDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaPathDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..6362d342690e49026d05978b4a973d0058c06ebe
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaPathDTO.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.metadata;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class MetaPathDTO {
+
+    @NotNull
+    private String resourceDefinitionUuid;
+
+    @NotNull
+    private String title;
+
+    @JsonInclude
+    private String parent = null;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaStateChangeDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaStateChangeDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2f3a8c71df32b6b6c2bd9816f39ded93bdc1c8f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaStateChangeDTO.java
@@ -0,0 +1,37 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.metadata;
+
+import lombok.*;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class MetaStateChangeDTO {
+
+    private MetadataState current;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaStateDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaStateDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c978717b21850b174a99623e486c26cde417fda
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/metadata/MetaStateDTO.java
@@ -0,0 +1,42 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.metadata;
+
+import lombok.*;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@ToString
+public class MetaStateDTO {
+
+    private MetadataState current;
+
+    private Map<String, MetadataState> children;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/reset/ResetDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/reset/ResetDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..136e88342f0d8275fd9aa61060864b602b275c29
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/reset/ResetDTO.java
@@ -0,0 +1,43 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.reset;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class ResetDTO {
+
+    private boolean users;
+
+    private boolean metadata;
+
+    private boolean resourceDefinitions;
+
+    private boolean settings;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/resource/ResourceDefinitionChangeDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/resource/ResourceDefinitionChangeDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..c957992ef848d86d6a0afb86c20f08b13c9926f0
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/resource/ResourceDefinitionChangeDTO.java
@@ -0,0 +1,60 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.resource;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.api.validator.ValidIri;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionLink;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class ResourceDefinitionChangeDTO {
+
+    @NotBlank
+    protected String name;
+
+    @NotNull
+    protected String urlPrefix;
+
+    @NotNull
+    protected List<String> shapeUuids;
+
+    @NotNull
+    @Valid
+    protected List<ResourceDefinitionChild> children;
+
+    @NotNull
+    @Valid
+    protected List<ResourceDefinitionLink> externalLinks;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/resource/ResourceDefinitionDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/resource/ResourceDefinitionDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa2c800ebf4234f1100c6622479b4f3c5dd54dba
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/resource/ResourceDefinitionDTO.java
@@ -0,0 +1,65 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.resource;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionLink;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class ResourceDefinitionDTO {
+
+    @NotBlank
+    private String uuid;
+
+    @NotBlank
+    protected String name;
+
+    @NotNull
+    protected String urlPrefix;
+
+    @NotNull
+    protected List<String> shapeUuids;
+
+    @NotNull
+    protected List<String> targetClassUris;
+
+    @NotNull
+    @Valid
+    protected List<ResourceDefinitionChild> children;
+
+    @NotNull
+    @Valid
+    protected List<ResourceDefinitionLink> externalLinks;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/search/SearchQueryDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/search/SearchQueryDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..9916c1f13158971e8ad690861d8707f6a57817dc
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/search/SearchQueryDTO.java
@@ -0,0 +1,41 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.search;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SearchQueryDTO {
+
+    @NotNull
+    private String q;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/search/SearchResultDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/search/SearchResultDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..22c4c8ea165abc3ebb321df51ecc10a781977313
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/search/SearchResultDTO.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.search;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.entity.search.SearchResultRelation;
+
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SearchResultDTO {
+
+    private String uri;
+
+    private List<String> types;
+
+    private String title;
+
+    private String description;
+
+    private List<SearchResultRelation> relations;
+
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsService.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsDTO.java
old mode 100755
new mode 100644
similarity index 64%
rename from src/main/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsService.java
rename to src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsDTO.java
index dd39043021e62f901ecb33bd52451212fa142b91..c59e31611372a281cf4817952037d714b94922fc
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsDTO.java
@@ -20,27 +20,29 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-/*
- * 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 nl.dtls.fairdatapoint.service.metadatametrics;
+package nl.dtls.fairdatapoint.api.dto.settings;
 
-import nl.dtls.fairdatapoint.entity.metadata.Metric;
-import org.eclipse.rdf4j.model.IRI;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.entity.settings.SettingsMetricsEntry;
 
-import javax.annotation.Nonnull;
 import java.util.List;
 
-public interface FairMetadataMetricsService {
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SettingsDTO {
+
+    private String clientUrl;
+
+    private String persistentUrl;
+
+    private List<SettingsMetricsEntry> metadataMetrics;
 
-    /**
-     * This method returns list of fair metrics for the given metadata URI
-     *
-     * @param metadataURI metadata URI
-     * @return List of fair metrics
-     */
-    List<Metric> getMetrics(@Nonnull IRI metadataURI);
+    private SettingsPingDTO ping;
 
+    private SettingsRepositoryDTO repository;
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsPingDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsPingDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef3ccd313d8e7f13c7b6631e54373c7e94ba9a3a
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsPingDTO.java
@@ -0,0 +1,47 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SettingsPingDTO {
+
+    @NotNull
+    private Boolean enabled;
+
+    @NotNull
+    private List<String> endpoints;
+
+    @NotNull
+    private String interval;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsPingUpdateDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsPingUpdateDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..46dadb201725ef05698b3c27c7338c19c6bd764c
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsPingUpdateDTO.java
@@ -0,0 +1,43 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.settings;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@Builder(toBuilder = true)
+public class SettingsPingUpdateDTO {
+
+    private boolean enabled;
+
+    @NotNull
+    private List<String> endpoints;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsRepositoryDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsRepositoryDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..16b1c57ab9847c96b8bcb255e3ccb0a96434f26b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsRepositoryDTO.java
@@ -0,0 +1,41 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SettingsRepositoryDTO {
+    private String type;
+    private String dir;
+    private String url;
+    private String repository;
+    private String username;
+    private String password;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsUpdateDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsUpdateDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..abb6f4dab187a66318a50e60abd96fb76aba9dde
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/settings/SettingsUpdateDTO.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.settings;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.entity.settings.SettingsMetricsEntry;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SettingsUpdateDTO {
+
+    @NotNull
+    private List<SettingsMetricsEntry> metadataMetrics;
+
+    @NotNull
+    private SettingsPingUpdateDTO ping;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeChangeDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeChangeDTO.java
index f992e0832603ee5754b62ff69232dc03a11bd5f7..625c7a8056ddbacf9c660f3472bf38d3a765fca9 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeChangeDTO.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeChangeDTO.java
@@ -28,6 +28,7 @@ import lombok.NoArgsConstructor;
 import lombok.Setter;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 
 @NoArgsConstructor
 @AllArgsConstructor
@@ -38,6 +39,8 @@ public class ShapeChangeDTO {
     @NotBlank
     private String name;
 
+    private boolean published;
+
     private String definition;
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeDTO.java
index 5193b904f2c53ab54a4e8efdfd5950a4736a7ff2..a9844dc51f1a56b6c371eab34a1211495dbc57b8 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeDTO.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeDTO.java
@@ -28,6 +28,8 @@ import lombok.NoArgsConstructor;
 import lombok.Setter;
 import nl.dtls.fairdatapoint.entity.shape.ShapeType;
 
+import java.util.List;
+
 @NoArgsConstructor
 @AllArgsConstructor
 @Getter
@@ -38,8 +40,11 @@ public class ShapeDTO {
 
     private String name;
 
+    private boolean published;
+
     private ShapeType type;
 
     private String definition;
 
+    private List<String> targetClasses;
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeRemoteDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeRemoteDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..8dfde6c8c84ffd82f8e83ff1c168d64cb62e0b5d
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/shape/ShapeRemoteDTO.java
@@ -0,0 +1,42 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.shape;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class ShapeRemoteDTO {
+    private String from;
+
+    private String uuid;
+
+    private String name;
+
+    private String definition;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/dto/user/UserProfileChangeDTO.java b/src/main/java/nl/dtls/fairdatapoint/api/dto/user/UserProfileChangeDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..307da1ff31cd26a544541aef07673b35cd86c311
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/dto/user/UserProfileChangeDTO.java
@@ -0,0 +1,48 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.dto.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class UserProfileChangeDTO {
+
+    @NotBlank
+    protected String firstName;
+
+    @NotBlank
+    protected String lastName;
+
+    @NotBlank
+    protected String email;
+
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/filter/CORSFilter.java b/src/main/java/nl/dtls/fairdatapoint/api/filter/CORSFilter.java
index 1a07a3eb8acdf962fe0b0fdc584622e5911925e5..187945c64d2c9e2a677993c1182d499e3a46dd52 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/filter/CORSFilter.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/filter/CORSFilter.java
@@ -55,7 +55,8 @@ public class CORSFilter extends OncePerRequestFilter {
         response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                 format("%s,%s,%s,%s", HttpHeaders.ORIGIN, HttpHeaders.AUTHORIZATION, HttpHeaders.ACCEPT,
                         HttpHeaders.CONTENT_TYPE));
-        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, format("%s", HttpHeaders.LOCATION));
+        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, format("%s,%s", HttpHeaders.LOCATION,
+                HttpHeaders.LINK));
         response.setHeader(HttpHeaders.ALLOW, allowedMtds);
         response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowedMtds);
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/filter/JwtTokenFilter.java b/src/main/java/nl/dtls/fairdatapoint/api/filter/JwtTokenFilter.java
index 3cc1d1b5dcbf18af8f0599e29e33b60bff4244ea..c1c534afaa09a1320de92bdccdb5fc8ba09cb1ab 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/filter/JwtTokenFilter.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/filter/JwtTokenFilter.java
@@ -25,6 +25,7 @@ package nl.dtls.fairdatapoint.api.filter;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
 import nl.dtls.fairdatapoint.entity.exception.UnauthorizedException;
+import nl.dtls.fairdatapoint.service.apikey.ApiKeyService;
 import nl.dtls.fairdatapoint.service.jwt.JwtService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -40,12 +41,17 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
+import static nl.dtls.fairdatapoint.util.HttpUtil.getToken;
+
 @Component
 public class JwtTokenFilter extends OncePerRequestFilter {
 
     @Autowired
     private JwtService jwtService;
 
+    @Autowired
+    private ApiKeyService apiKeyService;
+
     @Autowired
     private ObjectMapper objectMapper;
 
@@ -53,18 +59,38 @@ public class JwtTokenFilter extends OncePerRequestFilter {
     public void doFilterInternal(final HttpServletRequest request,
                                  final HttpServletResponse response, final FilterChain fc)
             throws IOException, ServletException {
+        String token = getToken(request);
+        if (tryWithUser(token) || tryWithApiKey(token)) {
+            fc.doFilter(request, response);
+        } else {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            response.setContentType(MediaType.APPLICATION_JSON.toString());
+            ErrorDTO error = new ErrorDTO(HttpStatus.UNAUTHORIZED, "You have to be log in");
+            objectMapper.writeValue(response.getWriter(), error);
+        }
+    }
+
+    private boolean tryWithUser(String token) {
         try {
-            String token = jwtService.resolveToken(request);
             if (token != null && jwtService.validateToken(token)) {
                 Authentication auth = jwtService.getAuthentication(token);
                 SecurityContextHolder.getContext().setAuthentication(auth);
             }
-            fc.doFilter(request, response);
+            return true;
         } catch (UnauthorizedException e) {
-            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-            response.setContentType(MediaType.APPLICATION_JSON.toString());
-            ErrorDTO error = new ErrorDTO(HttpStatus.UNAUTHORIZED, e.getMessage());
-            objectMapper.writeValue(response.getWriter(), error);
+            return false;
+        }
+    }
+
+    private boolean tryWithApiKey(String token) {
+        try {
+            if (token != null) {
+                Authentication auth = apiKeyService.getAuthentication(token);
+                SecurityContextHolder.getContext().setAuthentication(auth);
+            }
+            return true;
+        } catch (UnauthorizedException e) {
+            return false;
         }
     }
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/filter/LoggingFilter.java b/src/main/java/nl/dtls/fairdatapoint/api/filter/LoggingFilter.java
index 28801d60caac9399992b79ca729ed6b2d845467c..2840b7109d1fd396a0e8e973248b4008e0438fc0 100755
--- a/src/main/java/nl/dtls/fairdatapoint/api/filter/LoggingFilter.java
+++ b/src/main/java/nl/dtls/fairdatapoint/api/filter/LoggingFilter.java
@@ -27,7 +27,9 @@
  */
 package nl.dtls.fairdatapoint.api.filter;
 
+import nl.dtls.fairdatapoint.service.UtilityService;
 import org.apache.logging.log4j.ThreadContext;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.stereotype.Component;
 import org.springframework.web.filter.OncePerRequestFilter;
@@ -41,12 +43,15 @@ import java.io.IOException;
 @Component
 public class LoggingFilter extends OncePerRequestFilter {
 
+    @Autowired
+    private UtilityService utilityService;
+
     @Override
     public void doFilterInternal(final HttpServletRequest request,
                                  final HttpServletResponse response, final FilterChain fc)
             throws IOException, ServletException {
 
-        ThreadContext.put("ipAddress", request.getRemoteAddr());
+        ThreadContext.put("ipAddress", utilityService.getRemoteAddr(request));
         ThreadContext.put("responseStatus", String.valueOf(response.getStatus()));
         ThreadContext.put("requestMethod", request.getMethod());
         ThreadContext.put("requestURI", request.getRequestURI());
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/validator/DurationValidator.java b/src/main/java/nl/dtls/fairdatapoint/api/validator/DurationValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..618dbd846c83913b1efef9d39822dc9f18de7083
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/validator/DurationValidator.java
@@ -0,0 +1,47 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.validator;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import java.time.Duration;
+
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+public class DurationValidator implements ConstraintValidator<ValidDuration, String> {
+
+    @Override
+    public void initialize(ValidDuration text) {
+    }
+
+    @Override
+    public boolean isValid(String text, ConstraintValidatorContext cxt) {
+        try {
+            Duration.parse(text);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/validator/IriValidator.java b/src/main/java/nl/dtls/fairdatapoint/api/validator/IriValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..c348cf500872dda5a5ea47003be9388d95594a21
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/validator/IriValidator.java
@@ -0,0 +1,46 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.validator;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+public class IriValidator implements ConstraintValidator<ValidIri, String> {
+
+    @Override
+    public void initialize(ValidIri text) {
+    }
+
+    @Override
+    public boolean isValid(String text, ConstraintValidatorContext cxt) {
+        try {
+            i(text);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/validator/ValidDuration.java b/src/main/java/nl/dtls/fairdatapoint/api/validator/ValidDuration.java
new file mode 100644
index 0000000000000000000000000000000000000000..f46b8ac4e7ffc99db16d6b9855c191d9dd78a60b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/validator/ValidDuration.java
@@ -0,0 +1,40 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.validator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Documented
+@Constraint(validatedBy = DurationValidator.class)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ValidDuration {
+
+    String message() default "Invalid ISO-8601 duration";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/api/validator/ValidIri.java b/src/main/java/nl/dtls/fairdatapoint/api/validator/ValidIri.java
new file mode 100644
index 0000000000000000000000000000000000000000..25f1c0e3bc32a06cb51f8398b936bb4abb5133ca
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/api/validator/ValidIri.java
@@ -0,0 +1,40 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.api.validator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Documented
+@Constraint(validatedBy = IriValidator.class)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ValidIri {
+
+    String message() default "Invalid IRI";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/AclConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/AclConfig.java
index bc62eb8e91a9c0d029741f151b395afc4d6a9cd9..2a6c3bcb9a16a46c853e9503a1617a7efb7746d8 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/AclConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/AclConfig.java
@@ -43,14 +43,12 @@ import org.springframework.security.acls.mongodb.BasicLookupStrategy;
 import org.springframework.security.acls.mongodb.MongoDBMutableAclService;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 
-import java.util.List;
-
 import static java.lang.String.format;
 
 @Configuration
 public class AclConfig {
 
-    private static final String CACHE_NAME = "ACL_CACHE";
+    public static final String ACL_CACHE = "ACL_CACHE";
 
     @Autowired
     private MongoTemplate mongoTemplate;
@@ -60,8 +58,7 @@ public class AclConfig {
 
     @Bean
     public AclCache aclCache(ConcurrentMapCacheManager cacheManager) {
-        cacheManager.setCacheNames(List.of(CACHE_NAME));
-        Cache springCache = cacheManager.getCache(CACHE_NAME);
+        Cache springCache = cacheManager.getCache(ACL_CACHE);
         return new SpringCacheBasedAclCache(springCache, permissionGrantingStrategy(), aclAuthorizationStrategy());
     }
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/AsyncConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/AsyncConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..716504dccff306e0b66beb6a7067795d1e903e22
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/AsyncConfig.java
@@ -0,0 +1,34 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config;
+
+import nl.dtls.fairdatapoint.Profiles;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@Configuration
+@EnableAsync
+@Profile(Profiles.NON_TESTING)
+public class AsyncConfig {
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/CacheConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/CacheConfig.java
index aa2017a5480da11b850957203779c0f9a86e8c8a..61b5ed1555c0fda860c9827fb1c3e8788f509b17 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/CacheConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/CacheConfig.java
@@ -23,12 +23,43 @@
 package nl.dtls.fairdatapoint.config;
 
 import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.util.List;
+
+import static nl.dtls.fairdatapoint.config.AclConfig.ACL_CACHE;
+
 @Configuration
 @EnableCaching
 public class CacheConfig {
 
     public static final String CATALOG_THEMES_CACHE = "CATALOG_THEMES_CACHE";
 
+    public static final String RESOURCE_DEFINITION_CACHE = "RESOURCE_DEFINITION_CACHE";
+
+    public static final String RESOURCE_DEFINITION_PARENT_CACHE = "RESOURCE_DEFINITION_PARENT_CACHE";
+
+    public static final String RESOURCE_DEFINITION_TARGET_CLASSES_CACHE = "RESOURCE_DEFINITION_TARGET_CLASSES_CACHE";
+
+    public static final String LABEL_CACHE = "LABEL_CACHE";
+
+    public static final String SETTINGS_CACHE = "SETTINGS_CACHE";
+
+    @Bean
+    public ConcurrentMapCacheManager cacheManager() {
+        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
+        cacheManager.setCacheNames(List.of(
+                ACL_CACHE,
+                CATALOG_THEMES_CACHE,
+                RESOURCE_DEFINITION_CACHE,
+                RESOURCE_DEFINITION_PARENT_CACHE,
+                RESOURCE_DEFINITION_TARGET_CLASSES_CACHE,
+                SETTINGS_CACHE,
+                LABEL_CACHE
+        ));
+        return cacheManager;
+    }
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/MetricsValueConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/HttpClientConfig.java
old mode 100755
new mode 100644
similarity index 64%
rename from src/main/java/nl/dtls/fairdatapoint/config/MetricsValueConfig.java
rename to src/main/java/nl/dtls/fairdatapoint/config/HttpClientConfig.java
index 397530d6c8f9ef88ab5362a6bce8a5341aef8b26..cfc186cf682a2e8fc0d168b3cc60cbcf9984e1b7
--- a/src/main/java/nl/dtls/fairdatapoint/config/MetricsValueConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/HttpClientConfig.java
@@ -20,28 +20,33 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-/*
- * 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 nl.dtls.fairdatapoint.config;
 
-import org.springframework.beans.factory.config.YamlMapFactoryBean;
+import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
+import org.springframework.web.client.RestTemplate;
 
-import java.util.Map;
+import java.net.http.HttpClient;
+import java.time.Duration;
 
 @Configuration
-public class MetricsValueConfig {
+public class HttpClientConfig {
+
+    @Bean
+    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
+        return restTemplateBuilder
+                .setConnectTimeout(Duration.ofSeconds(5))
+                .setReadTimeout(Duration.ofSeconds(5))
+                .build();
+    }
 
-    @Bean(name = "metadataMetrics")
-    public Map<String, String> metadataMetrics() {
-        YamlMapFactoryBean yamlFactory = new YamlMapFactoryBean();
-        yamlFactory.setResources(new ClassPathResource("application.yml"));
-        return (Map<String, String>) yamlFactory.getObject().get("metadataMetrics");
+    @Bean
+    public HttpClient httpClient() {
+        return HttpClient.newBuilder()
+                .version(HttpClient.Version.HTTP_2)
+                .followRedirects(HttpClient.Redirect.ALWAYS)
+                .build();
     }
 
-}
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/MetadataConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/MetadataConfig.java
index 65b556d902e4864e37a0dd24ca7cbb8840020ad4..64894132905ebbd663bf299d87c0f4732fd5b180 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/MetadataConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/MetadataConfig.java
@@ -22,41 +22,25 @@
  */
 package nl.dtls.fairdatapoint.config;
 
-import nl.dtls.fairdatapoint.entity.metadata.Agent;
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
 import org.eclipse.rdf4j.model.IRI;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 import static nl.dtls.fairdatapoint.util.HttpUtil.removeLastSlash;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
-import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.l;
 
 @Configuration
 public class MetadataConfig {
 
-    @Value("${instance.clientUrl}")
-    private String clientUrl;
+    @Autowired
+    private InstanceProperties instanceProperties;
 
     @Bean(name = "persistentUrl")
-    public String persistentUrl(@Value("${instance.persistentUrl:}") String persistentUrl) {
-        if (persistentUrl == null || persistentUrl.isEmpty()) {
-            return clientUrl;
-        }
-        return removeLastSlash(persistentUrl);
-    }
-
-    @Bean(name = "publisher")
-    public Agent publisher(@Value("${metadataProperties.publisherURI:}") String publisherURI,
-                           @Value("${metadataProperties.publisherName:}") String publishername) {
-
-        Agent publisher = null;
-        if (!publisherURI.isEmpty() && !publishername.isEmpty()) {
-            publisher = new Agent();
-            publisher.setUri(i(publisherURI));
-            publisher.setName(l(publishername));
-        }
-        return publisher;
+    public String persistentUrl() {
+        return removeLastSlash(instanceProperties.getUrl());
     }
 
     @Bean(name = "language")
@@ -78,5 +62,4 @@ public class MetadataConfig {
         }
         return license;
     }
-
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/MongoConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/MongoConfig.java
index 1af85374596c350b76188013c0cbb67e1a5e6e1f..934890d77f9c70ea54ef065f7b37e5b7f5da6aec 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/MongoConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/MongoConfig.java
@@ -22,34 +22,41 @@
  */
 package nl.dtls.fairdatapoint.config;
 
-import com.github.mongobee.Mongobee;
+import com.github.cloudyrock.mongock.config.LegacyMigration;
+import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.SpringDataMongoV3Driver;
+import com.github.cloudyrock.spring.v5.MongockSpring5;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionTargetClassesCache;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.Environment;
 import org.springframework.data.mongodb.config.EnableMongoAuditing;
+import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
 
 @Configuration
 @EnableMongoAuditing
-@EnableMongoRepositories(basePackages = {"nl.dtls.fairdatapoint", "nl.dtls.rdf.migration", "org.springframework" +
-        ".security.acls"})
+@EnableMongoRepositories(basePackages = {"nl.dtls.fairdatapoint", "nl.dtls.rdf.migration", "org.springframework.security.acls"})
 public class MongoConfig {
 
-    @Value("${spring.data.mongodb.uri}")
-    private String mongoUri;
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
 
     @Autowired
-    private Environment environment;
+    private ResourceDefinitionTargetClassesCache targetClassesCache;
 
-    @Bean
-    public Mongobee mongobee() throws Exception {
-        Mongobee runner = new Mongobee(mongoUri);
-        runner.setChangeLogsScanPackage("nl.dtls.fairdatapoint");
-        runner.setSpringEnvironment(environment);
-        runner.execute();
-        return runner;
+    @Bean("mongockRunner")
+    public MongockSpring5.MongockApplicationRunner mongockApplicationRunner(
+            ApplicationContext springContext,
+            MongoTemplate mongoTemplate) {
+        return MongockSpring5.builder()
+                .setDriver(SpringDataMongoV3Driver.withDefaultLock(mongoTemplate))
+                .addChangeLogsScanPackage("nl.dtls.fairdatapoint.database.mongo.migration.production")
+                .setSpringContext(springContext)
+                .setLegacyMigration(new LegacyMigration("dbchangelog"))
+                .addDependency(ResourceDefinitionCache.class, resourceDefinitionCache)
+                .addDependency(ResourceDefinitionTargetClassesCache.class, targetClassesCache)
+                .buildApplicationRunner();
     }
-
 }
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/OpenApiConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/OpenApiConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..52db8d619243a8b0bc0aa22f54f4cdb1ee9740ca
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/OpenApiConfig.java
@@ -0,0 +1,93 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import io.swagger.v3.oas.models.tags.Tag;
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
+import nl.dtls.fairdatapoint.config.properties.OpenApiProperties;
+import nl.dtls.fairdatapoint.service.openapi.OpenApiTagsUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Configuration
+public class OpenApiConfig {
+
+    @Bean
+    public OpenAPI customOpenAPI(InstanceProperties instanceProperties, OpenApiProperties openApiProperties) {
+        OpenAPI openAPI = new OpenAPI()
+                .servers(Collections.singletonList(new Server().url(instanceProperties.getUrl())))
+                .components(new Components()
+                        .addSecuritySchemes("bearer-jwt", new SecurityScheme()
+                                .type(SecurityScheme.Type.HTTP)
+                                .scheme("bearer")
+                                .bearerFormat("JWT")
+                                .in(SecurityScheme.In.HEADER)
+                                .name("Authorization")
+                        )
+                )
+                .info(new Info()
+                        .title(openApiProperties.getTitle())
+                        .description(openApiProperties.getDescription())
+                        .version(openApiProperties.getVersion())
+                        .license(
+                                new License()
+                                        .name("The MIT License")
+                                        .url("https://opensource.org/licenses/MIT")
+                        )
+                )
+                .addSecurityItem(new SecurityRequirement()
+                        .addList("bearer-jwt", Arrays.asList("read", "write"))
+                )
+                .tags(OpenApiTagsUtils.STATIC_TAGS);
+        if (openApiProperties.getContact().getUrl() != null) {
+            openAPI.getInfo().contact(new Contact()
+                    .url(openApiProperties.getContact().getUrl())
+                    .name(openApiProperties.getContact().getName())
+                    .email(openApiProperties.getContact().getEmail())
+            );
+        }
+        openAPI.servers(Collections.singletonList(new Server().url(instanceProperties.getUrl())));
+        openAPI.setExtensions(new LinkedHashMap<>());
+        openAPI.getExtensions().put("fdpGenericPaths", new Paths());
+        openAPI.setPaths(new Paths());
+        return openAPI;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/RepositoryConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/RepositoryConfig.java
index 7bb749f68f4687c184bee27789ed5b0b393aba0a..32289614875e7121cbb492fdcf1bd872573caf48 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/RepositoryConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/RepositoryConfig.java
@@ -23,6 +23,7 @@
 package nl.dtls.fairdatapoint.config;
 
 import lombok.extern.log4j.Log4j2;
+import nl.dtls.fairdatapoint.config.properties.RepositoryProperties;
 import org.eclipse.rdf4j.repository.Repository;
 import org.eclipse.rdf4j.repository.RepositoryException;
 import org.eclipse.rdf4j.repository.config.RepositoryConfigException;
@@ -33,6 +34,7 @@ import org.eclipse.rdf4j.repository.sparql.SPARQLRepository;
 import org.eclipse.rdf4j.sail.Sail;
 import org.eclipse.rdf4j.sail.memory.MemoryStore;
 import org.eclipse.rdf4j.sail.nativerdf.NativeStore;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.SpringApplication;
 import org.springframework.context.ApplicationContext;
@@ -47,52 +49,21 @@ import static nl.dtls.fairdatapoint.util.HttpUtil.removeLastSlash;
 @Log4j2
 public class RepositoryConfig {
 
-    @Value("${repository.agraph.url:}")
-    private String agraphUrl;
-
-    @Value("${repository.agraph.username:}")
-    private String agraphUsername;
-
-    @Value("${repository.agraph.password:}")
-    private String agraphPassword;
-
-    @Value("${repository.graphDb.url:}")
-    private String graphDbUrl;
-
-    @Value("${repository.graphDb.repository:}")
-    private String graphDbRepository;
-
-    @Value("${repository.blazegraph.url:}")
-    private String blazegraphUrl;
-
-    @Value("${repository.blazegraph.repository:}")
-    private String blazegraphRepository;
-
-    @Value("${repository.native.dir:}")
-    private String nativeStoreDir;
+    @Autowired
+    private RepositoryProperties repositoryProperties;
 
     @Bean(initMethod = "initialize", destroyMethod = "shutDown")
-    public Repository repository(@Value("${repository.type:1}") int storeType, ApplicationContext context)
+    public Repository repository(ApplicationContext context)
             throws RepositoryException {
 
-        Repository repository = null;
-        switch (storeType) {
-            case 1:
-                repository = getInMemoryStore();
-                break;
-            case 2:
-                repository = getNativeStore();
-                break;
-            case 3:
-                repository = getAgraphRepository();
-                break;
-            case 4:
-                repository = getGraphDBRepository();
-                break;
-            case 5:
-                repository = getBlazeGraphRepository();
-                break;
-        }
+        Repository repository = switch (repositoryProperties.getType()) {
+            case 1 -> getInMemoryStore();
+            case 2 -> getNativeStore();
+            case 3 -> getAgraphRepository();
+            case 4 -> getGraphDBRepository();
+            case 5 -> getBlazeGraphRepository();
+            default -> null;
+        };
 
         if (repository == null) {
             log.error("Failed to configure a RDF repository");
@@ -107,43 +78,47 @@ public class RepositoryConfig {
     private Repository getInMemoryStore() {
         log.info("Setting up InMemory Store");
         Sail store = new MemoryStore();
-        SailRepository repository = new SailRepository(store);
-        return repository;
+        return new SailRepository(store);
     }
 
     private Repository getNativeStore() {
         log.info("Setting up Native Store");
-        if (!nativeStoreDir.isEmpty()) {
-            File dataDir = new File(nativeStoreDir);
+        if (!repositoryProperties.getNativeRepo().getDir().isEmpty()) {
+            File dataDir = new File(repositoryProperties.getNativeRepo().getDir());
             return new SailRepository(new NativeStore(dataDir));
         }
-        log.warn("'nativeStoreDir' is empty");
+        log.warn("'repository.native.dir' is empty");
         return null;
     }
 
     private Repository getAgraphRepository() {
         log.info("Setting up Allegro Graph Store");
-        if (!agraphUrl.isEmpty()) {
-            SPARQLRepository repository = new SPARQLRepository(agraphUrl);
-            if (!agraphUsername.isEmpty() && !agraphPassword.isEmpty()) {
-                repository.setUsernameAndPassword(agraphUsername, agraphPassword);
+        if (!repositoryProperties.getAgraph().getUrl().isEmpty()) {
+            SPARQLRepository repository = new SPARQLRepository(repositoryProperties.getAgraph().getUrl());
+            if (!repositoryProperties.getAgraph().getUsername().isEmpty() &&
+                    !repositoryProperties.getAgraph().getPassword().isEmpty()) {
+                repository.setUsernameAndPassword(
+                        repositoryProperties.getAgraph().getUsername(),
+                        repositoryProperties.getAgraph().getPassword()
+                );
             }
             return repository;
         }
-        log.warn("'agraphUrl' is empty");
+        log.warn("'repository.agraph.url' is empty");
         return null;
     }
 
     private Repository getBlazeGraphRepository() {
         log.info("Setting up Blaze Graph Store");
+        String blazegraphUrl = repositoryProperties.getBlazegraph().getUrl();
         if (!blazegraphUrl.isEmpty()) {
             blazegraphUrl = removeLastSlash(blazegraphUrl);
             // Build url for blazegraph (Eg: http://localhost:8079/bigdata/namespace/test1/sparql)
             StringBuilder sb = new StringBuilder();
             sb.append(blazegraphUrl);
             sb.append("/namespace/");
-            if (!blazegraphRepository.isEmpty()) {
-                sb.append(blazegraphRepository);
+            if (!repositoryProperties.getBlazegraph().getRepository().isEmpty()) {
+                sb.append(repositoryProperties.getBlazegraph().getRepository());
             } else {
                 sb.append("kb");
             }
@@ -151,18 +126,34 @@ public class RepositoryConfig {
             String url = sb.toString();
             return new SPARQLRepository(url);
         }
-        log.warn("'blazegraphUrl' is empty");
+        log.warn("'repository.blazegraph.url' is empty");
         return null;
     }
 
     private Repository getGraphDBRepository() {
         log.info("Setting up GraphDB Store");
         try {
-            if (!graphDbUrl.isEmpty() && !graphDbRepository.isEmpty()) {
-                RepositoryManager repositoryManager = new RemoteRepositoryManager(graphDbUrl);
-                repositoryManager.initialize();
-                return repositoryManager.getRepository(graphDbRepository);
+            if (!repositoryProperties.getGraphDb().getUrl().isEmpty() &&
+                    !repositoryProperties.getGraphDb().getRepository().isEmpty()) {
+                final RepositoryManager repositoryManager;
+                if (!repositoryProperties.getGraphDb().getUsername().isEmpty() &&
+                        !repositoryProperties.getGraphDb().getPassword().isEmpty()) {
+                    repositoryManager = RemoteRepositoryManager.getInstance(
+                            repositoryProperties.getGraphDb().getUrl(),
+                            repositoryProperties.getGraphDb().getUsername(),
+                            repositoryProperties.getGraphDb().getPassword()
+                    );
+                } else {
+                    repositoryManager = RemoteRepositoryManager.getInstance(
+                            repositoryProperties.getGraphDb().getUrl()
+                    );
+                }
+                
+                return repositoryManager.getRepository(
+                        repositoryProperties.getGraphDb().getRepository()
+                );
             }
+            log.warn("'repository.graphDb.url' or 'repository.graphDb.repository' is empty");
         } catch (RepositoryConfigException | RepositoryException e) {
             log.error("Failed to connect to GraphDB");
         }
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/RepositoryMigrationConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/RepositoryMigrationConfig.java
index df7b4aed92a7ed33cb9210c6e7479a467504b505..c37735fe281e3a0023add32b67465df1282366fc 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/RepositoryMigrationConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/RepositoryMigrationConfig.java
@@ -35,7 +35,7 @@ import org.springframework.context.annotation.Profile;
 public class RepositoryMigrationConfig {
 
     @Bean
-    @DependsOn("mongobee")
+    @DependsOn("mongockRunner")
     @Profile(Profiles.PRODUCTION)
     public RdfProductionMigrationRunner rdfProductionMigrationRunner(RdfMigrationRepository rdfMigrationRepository,
                                                                      ApplicationContext appContext) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/SecurityConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/SecurityConfig.java
index cb6fe05b6743ca02728ab10a813b80e7d5e9fbe3..c78f034c8937a11a164bb5bf98804d77f6a11f5c 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/SecurityConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/SecurityConfig.java
@@ -55,11 +55,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                 .antMatchers("/dashboard").authenticated()
                 .antMatchers("/users**").authenticated()
+                .antMatchers("/api-keys**").authenticated()
                 .antMatchers("/users/**").authenticated()
                 .antMatchers("/memberships**").authenticated()
                 .antMatchers("/tokens").permitAll()
+                .antMatchers("/search**").permitAll()
+                .antMatchers("/index/admin**").authenticated()
+                .antMatchers("/index**").permitAll()
                 .antMatchers(HttpMethod.PUT, "/**").authenticated()
-                .antMatchers(HttpMethod.POST, "/**").authenticated()
                 .anyRequest().permitAll()
                 .and()
                 .apply(filterConfigurer);
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/SwaggerConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/SwaggerConfig.java
deleted file mode 100755
index 80ea0aad14ad4566cbba25a488b3e92a69ffd2a9..0000000000000000000000000000000000000000
--- a/src/main/java/nl/dtls/fairdatapoint/config/SwaggerConfig.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 DTL
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-/*
- * 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 nl.dtls.fairdatapoint.config;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.*;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spi.service.contexts.SecurityContext;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-import java.util.Collections;
-import java.util.List;
-
-import static nl.dtls.fairdatapoint.util.HttpUtil.removeProtocol;
-
-@Configuration
-@EnableSwagger2
-public class SwaggerConfig {
-
-    @Value("${instance.clientUrl}")
-    private String clientUrl;
-
-    @Bean
-    public Docket api() {
-        return new Docket(DocumentationType.SWAGGER_2)
-                .select()
-                .apis(RequestHandlerSelectors.any())
-                .paths(PathSelectors.any())
-                .build()
-                .apiInfo(apiInfo())
-                .host(removeProtocol(clientUrl))
-                .securitySchemes(List.of(apiKey()))
-                .securityContexts(List.of(securityContext()));
-    }
-
-    private ApiInfo apiInfo() {
-        return new ApiInfo(
-                "FAIR Data Point API",
-                "<a target='_blank' " +
-                        "    href='https://github.com/FAIRDataTeam/FAIRDataPoint/wiki/FAIR-Data-Point-Specification'>" +
-                        "       FAIR Data Point Specification" +
-                        "   </a>" +
-                        "   <br/>",
-                "1.3.0",
-                "ATO",
-                new Contact("Luiz Bonino",
-                        "https://github.com/FAIRDataTeam/FAIRDataPoint",
-                        "luiz.bonino@go-fair.org"),
-                "The MIT License",
-                "https://opensource.org/licenses/MIT",
-                Collections.emptyList()
-        );
-    }
-
-    private ApiKey apiKey() {
-        return new ApiKey("apiKey", "Authorization", "header");
-    }
-
-    private SecurityContext securityContext() {
-        return SecurityContext.builder()
-                .securityReferences(defaultAuth())
-                .forPaths(PathSelectors.any())
-                .build();
-    }
-
-    private List<SecurityReference> defaultAuth() {
-        AuthorizationScope[] authorizationScopes = {new AuthorizationScope("global", "global description")};
-        return List.of(new SecurityReference("apiKey", authorizationScopes));
-    }
-
-}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/WebConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/WebConfig.java
index bba4dae3c1df0540347dccda8ff992a063e98959..3a985c09b6f2b849058e8cfbc7b3223f5418c865 100755
--- a/src/main/java/nl/dtls/fairdatapoint/config/WebConfig.java
+++ b/src/main/java/nl/dtls/fairdatapoint/config/WebConfig.java
@@ -22,6 +22,7 @@
  */
 package nl.dtls.fairdatapoint.config;
 
+import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import nl.dtls.fairdatapoint.api.converter.ErrorConverter;
 import nl.dtls.fairdatapoint.api.converter.RdfConverter;
@@ -35,11 +36,13 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
 import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.resource.WebJarsResourceResolver;
+import org.springframework.web.servlet.view.InternalResourceViewResolver;
 
 import java.util.List;
 
 import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
-import static com.fasterxml.jackson.databind.MapperFeature.SORT_PROPERTIES_ALPHABETICALLY;
+import static org.springdoc.core.Constants.CLASSPATH_RESOURCE_LOCATION;
 
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
@@ -52,10 +55,10 @@ public class WebConfig implements WebMvcConfigurer {
 
     @Override
     public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        converters.add(new StringHttpMessageConverter());
         converters.addAll(errorConverters);
         converters.addAll(rdfConverters);
-        converters.add(new MappingJackson2HttpMessageConverter());
-        converters.add(new StringHttpMessageConverter());
+        converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
     }
 
     @Override
@@ -66,17 +69,16 @@ public class WebConfig implements WebMvcConfigurer {
         for (RdfConverter converter : rdfConverters) {
             converter.configureContentNegotiation(configurer);
         }
-        configurer.favorPathExtension(false);
         configurer.favorParameter(true);
     }
 
     @Override
     public void addResourceHandlers(final ResourceHandlerRegistry registry) {
         // Swagger
-        registry.setOrder(Integer.MIN_VALUE + 1).addResourceHandler("/swagger-ui.html")
-                .addResourceLocations("classpath:/META-INF/resources/");
-        registry.setOrder(Integer.MIN_VALUE + 2).addResourceHandler("/webjars/**")
-                .addResourceLocations("classpath:/META-INF/resources/webjars/");
+        registry.setOrder(Integer.MIN_VALUE + 1).addResourceHandler("/webjars/**")
+                .addResourceLocations(CLASSPATH_RESOURCE_LOCATION+"/webjars/")
+                .resourceChain(true)
+                .addResolver(new WebJarsResourceResolver());
     }
 
     @Bean
@@ -85,8 +87,12 @@ public class WebConfig implements WebMvcConfigurer {
         final ObjectMapper mapper = new ObjectMapper();
         mapper.findAndRegisterModules();
         mapper.setSerializationInclusion(NON_NULL);
-        mapper.enable(SORT_PROPERTIES_ALPHABETICALLY);
+        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
         return mapper;
     }
 
+    @Bean
+    public InternalResourceViewResolver defaultViewResolver() {
+        return new InternalResourceViewResolver();
+    }
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/WorkerConfig.java b/src/main/java/nl/dtls/fairdatapoint/config/WorkerConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..982d72d18012aed2bfa85cd2cd1e597fd6a5109b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/WorkerConfig.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@Configuration
+@EnableScheduling
+public class WorkerConfig {
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/InstanceProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/InstanceProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e8fe4bebb83d1e5542de6e19866fd0cd7c566ea
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/InstanceProperties.java
@@ -0,0 +1,48 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "instance")
+public class InstanceProperties {
+    private String clientUrl = "http://localhost:8080";
+    private String persistentUrl = null;
+    private boolean behindProxy = true;
+    private boolean index = false;
+
+    public String getUrl() {
+        if (persistentUrl == null || persistentUrl.isBlank()) {
+            return clientUrl;
+        }
+        return persistentUrl;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/OpenApiContactProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/OpenApiContactProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..f289ddab6aaa0cf959a1789294bf3b9717e84975
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/OpenApiContactProperties.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class OpenApiContactProperties {
+    private String name;
+    private String email;
+    private String url;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/OpenApiProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/OpenApiProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..1cc5d10cec2cf168a16018cbf4b9f8e6ebb761d9
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/OpenApiProperties.java
@@ -0,0 +1,41 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "openapi")
+public class OpenApiProperties {
+    private String title;
+    private String version;
+    private String description;
+    private OpenApiContactProperties contact;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/PingProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/PingProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5737fb61e28365dd8d721bf79d948c1079e3e95
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/PingProperties.java
@@ -0,0 +1,42 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.time.Duration;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "ping")
+public class PingProperties {
+    private boolean enabled = true;
+    private String initDelay = "10000";
+    private Duration interval = Duration.ofDays(7);
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryBasicProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryBasicProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..15481d3aef7b3306aab7852bb65fcc3c276950cd
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryBasicProperties.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class RepositoryBasicProperties {
+    private String url = "";
+    private String repository = "";
+    private String username = "";
+    private String password = "";
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryNativeProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryNativeProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..c825379bdc4593b511da4805987127ca35f85a2b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryNativeProperties.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class RepositoryNativeProperties {
+    private String dir = "";
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryProperties.java b/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..b26d71429fa95ffc03c3b987003a0b114e7d3bb8
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/config/properties/RepositoryProperties.java
@@ -0,0 +1,98 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "repository")
+public class RepositoryProperties {
+    private int type;
+    private RepositoryNativeProperties nativeRepo = null;
+    private RepositoryBasicProperties agraph = null;
+    private RepositoryBasicProperties graphDb = null;
+    private RepositoryBasicProperties blazegraph = null;
+
+    public void setNative(RepositoryNativeProperties nativeRepo) {
+        this.nativeRepo = nativeRepo;
+    }
+
+    public String getStringType() {
+        return switch (type) {
+            case 1 -> "InMemory";
+            case 2 -> "Native";
+            case 3 -> "AllegroGraph";
+            case 4 -> "GraphDB";
+            case 5 -> "Blazegraph";
+            default -> "Invalid";
+        };
+    }
+
+    public String getDir() {
+        if (type == 2) {
+            return nativeRepo.getDir();
+        }
+        return null;
+    }
+
+    public String getUrl() {
+        return switch (type) {
+            case 3 -> agraph.getUrl();
+            case 4 -> graphDb.getUrl();
+            case 5 -> blazegraph.getUrl();
+            default -> null;
+        };
+    }
+
+    public String getRepository() {
+        return switch (type) {
+            case 3 -> agraph.getRepository();
+            case 4 -> graphDb.getRepository();
+            case 5 -> blazegraph.getRepository();
+            default -> null;
+        };
+    }
+
+    public String getUsername() {
+        return switch (type) {
+            case 3 -> agraph.getUsername();
+            case 4 -> graphDb.getUsername();
+            default -> null;
+        };
+    }
+
+    public String getPassword() {
+        return switch (type) {
+            case 3 -> agraph.getPassword();
+            case 4 -> graphDb.getPassword();
+            default -> null;
+        };
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/MigrationRunner.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/MigrationRunner.java
index dd6ae9da5ff5afa84c8d3260bbbf52891223963f..e816fea38900b3fdad41b2d21b0585a58a96cd53 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/MigrationRunner.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/MigrationRunner.java
@@ -24,7 +24,11 @@ package nl.dtls.fairdatapoint.database.mongo.migration.development;
 
 import nl.dtls.fairdatapoint.Profiles;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.acl.AclMigration;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.apikey.ApiKeyMigration;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.index.entry.IndexEntryMigration;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.index.event.EventMigration;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.membership.MembershipMigration;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.MetadataMigration;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.ResourceDefinitionMigration;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.shape.ShapeMigration;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.user.UserMigration;
@@ -53,6 +57,18 @@ public class MigrationRunner {
     @Autowired
     private ShapeMigration shapeMigration;
 
+    @Autowired
+    private ApiKeyMigration apiKeyMigration;
+
+    @Autowired
+    private MetadataMigration metadataMigration;
+
+    @Autowired
+    private IndexEntryMigration indexEntryMigration;
+
+    @Autowired
+    private EventMigration eventMigration;
+
     @PostConstruct
     public void run() {
         userMigration.runMigration();
@@ -60,6 +76,10 @@ public class MigrationRunner {
         aclMigration.runMigration();
         resourceDefinitionMigration.runMigration();
         shapeMigration.runMigration();
+        apiKeyMigration.runMigration();
+        metadataMigration.runMigration();
+        indexEntryMigration.runMigration();
+        eventMigration.runMigration();
     }
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/apikey/ApiKeyMigration.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/apikey/ApiKeyMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..81ed7f163f57b10ad2ac328a73f972ad7c2d21f9
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/apikey/ApiKeyMigration.java
@@ -0,0 +1,46 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.apikey;
+
+import nl.dtls.fairdatapoint.database.common.migration.Migration;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.apikey.data.ApiKeyFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ApiKeyRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ApiKeyMigration implements Migration {
+
+    @Autowired
+    private ApiKeyFixtures apiKeyFixtures;
+
+    @Autowired
+    private ApiKeyRepository apiKeyRepository;
+
+    public void runMigration() {
+        apiKeyRepository.deleteAll();
+        apiKeyRepository.save(apiKeyFixtures.albertApiKey());
+        apiKeyRepository.save(apiKeyFixtures.nikolaApiKey());
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/apikey/data/ApiKeyFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/apikey/data/ApiKeyFixtures.java
new file mode 100644
index 0000000000000000000000000000000000000000..16fe31cc8e6ba044fb7d2b1a7aa6170006a30cec
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/apikey/data/ApiKeyFixtures.java
@@ -0,0 +1,58 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.apikey.data;
+
+import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
+import nl.dtls.fairdatapoint.entity.apikey.ApiKey;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ApiKeyFixtures {
+
+    @Autowired
+    private UserFixtures userFixtures;
+
+    public final String ALBERT_API_KEY = "a274793046e34a219fd0ea6362fcca61a001500b71724f4c973a017031653c20";
+
+    public final String NIKOLA_API_KEY = "dd5dc3b53b6145cfa9f6c58b72ebad21cd2f860ace62451ba4e3c74a0e63540a";
+
+    public ApiKey albertApiKey() {
+        return new ApiKey(
+                null,
+                "a1c00673-24c5-4e0a-bdbe-22e961ee7548",
+                userFixtures.albert().getUuid(),
+                ALBERT_API_KEY
+        );
+    }
+
+    public ApiKey nikolaApiKey() {
+        return new ApiKey(
+                null,
+                "62657760-21fe-488c-a0ea-f612a70493da",
+                userFixtures.nikola().getUuid(),
+                NIKOLA_API_KEY
+        );
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/entry/IndexEntryMigration.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/entry/IndexEntryMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..587336676b936ee3880f7fa238986c96d8f537f2
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/entry/IndexEntryMigration.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.index.entry;
+
+import nl.dtls.fairdatapoint.database.common.migration.Migration;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.index.entry.data.IndexEntryFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class IndexEntryMigration implements Migration {
+
+    @Autowired
+    private IndexEntryFixtures indexEntryFixtures;
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    public void runMigration() {
+        indexEntryRepository.deleteAll();
+
+        indexEntryRepository.save(indexEntryFixtures.entryActive());
+        indexEntryRepository.save(indexEntryFixtures.entryActive2());
+        indexEntryRepository.save(indexEntryFixtures.entryInactive());
+        indexEntryRepository.save(indexEntryFixtures.entryUnreachable());
+        indexEntryRepository.save(indexEntryFixtures.entryInvalid());
+        indexEntryRepository.save(indexEntryFixtures.entryUnknown());
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/entry/data/IndexEntryFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/entry/data/IndexEntryFixtures.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7e91361c1d00f878a01a7f78a6fc5404ac8008d
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/entry/data/IndexEntryFixtures.java
@@ -0,0 +1,105 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.index.entry.data;
+
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.entry.RepositoryMetadata;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.util.HashMap;
+
+import static nl.dtls.fairdatapoint.entity.index.entry.IndexEntryState.*;
+
+@Service
+public class IndexEntryFixtures {
+
+    public IndexEntry entryActive() {
+        String clientUri = "https://example.com/my-valid-fairdatapoint";
+        RepositoryMetadata repositoryData = new RepositoryMetadata(
+                RepositoryMetadata.CURRENT_VERSION,
+                clientUri,
+                new HashMap<>()
+        );
+        return new IndexEntry("8987abc1-15a4-4752-903c-8f8a5882cca6", clientUri, Valid, Instant.now(), Instant.now(),
+                Instant.now(), repositoryData);
+    }
+
+    public IndexEntry entryActive2() {
+        String clientUri = "https://app.fairdatapoint.org";
+        RepositoryMetadata repositoryData = new RepositoryMetadata(
+                RepositoryMetadata.CURRENT_VERSION,
+                clientUri,
+                new HashMap<>()
+        );
+        Instant date = Instant.parse("2020-05-30T23:38:23.085Z");
+        return new IndexEntry("c912331f-4a77-4300-a469-dbaf5fc0b4e2", clientUri, Valid, date, date, date,
+                repositoryData);
+    }
+
+    public IndexEntry entryInactive() {
+        String clientUri = "https://example.com/my-valid-fairdatapoint";
+        RepositoryMetadata repositoryData = new RepositoryMetadata(
+                RepositoryMetadata.CURRENT_VERSION,
+                clientUri,
+                new HashMap<>()
+        );
+        Instant date = Instant.parse("2020-05-30T23:38:23.085Z");
+        return new IndexEntry("b5851ebe-aacf-4de9-bf0a-3686e9256e73", clientUri, Valid, date, date, date,
+                repositoryData);
+    }
+
+    public IndexEntry entryUnreachable() {
+        String clientUri = "https://example.com/my-unreachable-fairdatapoint";
+        RepositoryMetadata repositoryData = new RepositoryMetadata(
+                RepositoryMetadata.CURRENT_VERSION,
+                clientUri,
+                new HashMap<>()
+        );
+        return new IndexEntry("dae46b47-87fb-4fdf-995c-8aa3739a27fc", clientUri, Unreachable, Instant.now(),
+                Instant.now(), Instant.now(), repositoryData);
+    }
+
+    public IndexEntry entryInvalid() {
+        String clientUri = "https://example.com/my-invalid-fairdatapoint";
+        RepositoryMetadata repositoryData = new RepositoryMetadata(
+                RepositoryMetadata.CURRENT_VERSION,
+                clientUri,
+                new HashMap<>()
+        );
+        return new IndexEntry("b37e8c1f-ac0e-49f8-8e07-35571c4f8235", clientUri, Invalid, Instant.now(),
+                Instant.now(), Instant.now(), repositoryData);
+    }
+
+    public IndexEntry entryUnknown() {
+        String clientUri = "https://example.com/my-unknown-fairdatapoint";
+        RepositoryMetadata repositoryData = new RepositoryMetadata(
+                RepositoryMetadata.CURRENT_VERSION,
+                clientUri,
+                new HashMap<>()
+        );
+        return new IndexEntry("4471d7c5-8c5b-4581-a9bc-d175456492c4", clientUri, Unknown, Instant.now(),
+                Instant.now(), Instant.now(), repositoryData);
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/event/EventMigration.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/event/EventMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e7768c0a1a1861b077df705724723c7f08d312e
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/index/event/EventMigration.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.index.event;
+
+import nl.dtls.fairdatapoint.database.mongo.repository.EventRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class EventMigration {
+
+    @Autowired
+    private EventRepository eventRepository;
+
+    public void runMigration() {
+        eventRepository.deleteAll();
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/membership/data/MembershipFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/membership/data/MembershipFixtures.java
index 5b4544b5c107c3b8ae63deabe8b0e1b1c4ddc23f..726dfc0043db15148d032baaec43fc6807d58e85 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/membership/data/MembershipFixtures.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/membership/data/MembershipFixtures.java
@@ -22,14 +22,13 @@
  */
 package nl.dtls.fairdatapoint.database.mongo.migration.development.membership.data;
 
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
 import nl.dtls.fairdatapoint.entity.membership.Membership;
 import nl.dtls.fairdatapoint.entity.membership.MembershipPermission;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 
-import static nl.dtls.fairdatapoint.entity.membership.MembershipEntity.*;
-
 @Service
 public class MembershipFixtures {
 
@@ -50,9 +49,9 @@ public class MembershipFixtures {
                     add(ADMINISTRATION);
                 }},
                 new ArrayList<>() {{
-                    add(CATALOG);
-                    add(DATASET);
-                    add(DISTRIBUTION);
+                    add(ResourceDefinitionFixtures.CATALOG_DEFINITION_UUID);
+                    add(ResourceDefinitionFixtures.DATASET_DEFINITION_UUID);
+                    add(ResourceDefinitionFixtures.DISTRIBUTION_DEFINITION_UUID);
                 }}
         );
     }
@@ -65,7 +64,7 @@ public class MembershipFixtures {
                     add(CREATE);
                 }},
                 new ArrayList<>() {{
-                    add(CATALOG);
+                    add(ResourceDefinitionFixtures.CATALOG_DEFINITION_UUID);
                 }}
         );
     }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/metadata/MetadataMigration.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/metadata/MetadataMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..695fca295a624ed967982b3f5eb7d8e833f34192
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/metadata/MetadataMigration.java
@@ -0,0 +1,40 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.metadata;
+
+import nl.dtls.fairdatapoint.database.common.migration.Migration;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MetadataMigration implements Migration {
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    public void runMigration() {
+        metadataRepository.deleteAll();
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/metadata/data/MetadataFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/metadata/data/MetadataFixtures.java
new file mode 100644
index 0000000000000000000000000000000000000000..823318d2085b8d1375159c338e33ba5782293b0e
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/metadata/data/MetadataFixtures.java
@@ -0,0 +1,101 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data;
+
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MetadataFixtures {
+
+    @Autowired
+    @Qualifier("persistentUrl")
+    private String persistentUrl;
+
+    public Metadata repositoryMetadata() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl,
+                        MetadataState.PUBLISHED
+                );
+    }
+
+    public Metadata catalog1() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl + "/catalog/catalog-1",
+                        MetadataState.PUBLISHED
+                );
+    }
+
+    public Metadata catalog2() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl + "/catalog/catalog-2",
+                        MetadataState.DRAFT
+                );
+    }
+
+    public Metadata dataset1() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl + "/dataset/dataset-1",
+                        MetadataState.PUBLISHED
+                );
+    }
+
+    public Metadata dataset2() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl + "/dataset/dataset-2",
+                        MetadataState.DRAFT
+                );
+    }
+
+    public Metadata distribution1() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl + "/distribution/distribution-1",
+                        MetadataState.PUBLISHED
+                );
+    }
+
+    public Metadata distribution2() {
+        return
+                new Metadata(
+                        null,
+                        persistentUrl + "/distribution/distribution-2",
+                        MetadataState.DRAFT
+                );
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/ResourceDefinitionMigration.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/ResourceDefinitionMigration.java
index 447437d1ece756257a9fc9f79b4e7dafe347867e..3ed4b6f8973ba1949bab79cedde782c847fcd152 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/ResourceDefinitionMigration.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/ResourceDefinitionMigration.java
@@ -26,6 +26,7 @@ import nl.dtls.fairdatapoint.database.common.migration.Migration;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
 import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -38,6 +39,9 @@ public class ResourceDefinitionMigration implements Migration {
     @Autowired
     private ResourceDefinitionRepository resourceDefinitionRepository;
 
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
+
     public void runMigration() {
         resourceDefinitionRepository.deleteAll();
 
@@ -52,6 +56,8 @@ public class ResourceDefinitionMigration implements Migration {
 
         ResourceDefinition distributionDef = resourceDefinitionFixtures.distributionDefinition();
         resourceDefinitionRepository.save(distributionDef);
+
+        resourceDefinitionCache.computeCache();
     }
 
 }
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/data/ResourceDefinitionFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/data/ResourceDefinitionFixtures.java
index fa39cf178734a2fd4269a7df452a3740217527b8..b0644ba0ea03f07400ef77f1b0218d6b8e5b633b 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/data/ResourceDefinitionFixtures.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/resource/data/ResourceDefinitionFixtures.java
@@ -22,7 +22,8 @@
  */
 package nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data;
 
-import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.shape.data.ShapeFixtures;
+import nl.dtls.fairdatapoint.entity.resource.*;
 import nl.dtls.fairdatapoint.vocabulary.R3D;
 import org.eclipse.rdf4j.model.vocabulary.DCAT;
 import org.springframework.stereotype.Service;
@@ -40,31 +41,43 @@ public class ResourceDefinitionFixtures {
 
     public static String DISTRIBUTION_DEFINITION_UUID = "02c649de-c579-43bb-b470-306abdc808c7";
 
+    public static String ONTOLOGY_DEFINITION_UUID = "4bc19f45-845d-48d6-ade7-ac2664563f60";
+
     public ResourceDefinition repositoryDefinition() {
         return new ResourceDefinition(
                 REPOSITORY_DEFINITION_UUID,
                 "Repository",
                 "",
-                "http://www.re3data.org/schema/3-0#Repository",
-                "https://www.purl.org/fairtools/fdp/schema/0.1/fdpMetadata",
-                List.of("http://www.w3.org/ns/dcat#Resource", "http://www.re3data.org/schema/3-0#Repository"),
-                R3D.DATACATALOG.stringValue(),
-                null,
-                CATALOG_DEFINITION_UUID
+                List.of(ShapeFixtures.RESOURCE_SHAPE_UUID, ShapeFixtures.REPOSITORY_SHAPE_UUID),
+                List.of(new ResourceDefinitionChild(
+                        CATALOG_DEFINITION_UUID,
+                        R3D.DATACATALOG.stringValue(),
+                        new ResourceDefinitionChildListView(
+                                "Catalogs",
+                                "http://www.w3.org/ns/dcat#themeTaxonomy",
+                                List.of()
+                        )
+                )),
+                List.of()
         );
     }
 
     public ResourceDefinition catalogDefinition() {
         return new ResourceDefinition(
-                "a0949e72-4466-4d53-8900-9436d1049a4b",
+                CATALOG_DEFINITION_UUID,
                 "Catalog",
                 "catalog",
-                "http://www.w3.org/ns/dcat#Catalog",
-                "https://www.purl.org/fairtools/fdp/schema/0.1/catalogMetadata",
-                List.of("http://www.w3.org/ns/dcat#Resource", "http://www.w3.org/ns/dcat#Catalog"),
-                DCAT.HAS_DATASET.stringValue(),
-                REPOSITORY_DEFINITION_UUID,
-                DATASET_DEFINITION_UUID
+                List.of(ShapeFixtures.RESOURCE_SHAPE_UUID, ShapeFixtures.CATALOG_SHAPE_UUID),
+                List.of(new ResourceDefinitionChild(
+                        DATASET_DEFINITION_UUID,
+                        DCAT.HAS_DATASET.stringValue(),
+                        new ResourceDefinitionChildListView(
+                                "Datasets",
+                                "http://www.w3.org/ns/dcat#theme",
+                                List.of()
+                        )
+                )),
+                List.of()
         );
     }
 
@@ -73,12 +86,18 @@ public class ResourceDefinitionFixtures {
                 DATASET_DEFINITION_UUID,
                 "Dataset",
                 "dataset",
-                "http://www.w3.org/ns/dcat#Dataset",
-                "https://www.purl.org/fairtools/fdp/schema/0.1/datasetMetadata",
-                List.of("http://www.w3.org/ns/dcat#Resource", "http://www.w3.org/ns/dcat#Dataset"),
-                DCAT.HAS_DISTRIBUTION.stringValue(),
-                CATALOG_DEFINITION_UUID,
-                DISTRIBUTION_DEFINITION_UUID
+                List.of(ShapeFixtures.RESOURCE_SHAPE_UUID, ShapeFixtures.DATASET_SHAPE_UUID),
+                List.of(new ResourceDefinitionChild(
+                        DISTRIBUTION_DEFINITION_UUID,
+                        DCAT.HAS_DISTRIBUTION.stringValue(),
+                        new ResourceDefinitionChildListView("Distributions", null, List.of(
+                                new ResourceDefinitionChildListViewMetadata(
+                                        "Media Type",
+                                        "http://www.w3.org/ns/dcat#mediaType"
+                                )
+                        ))
+                )),
+                List.of()
         );
     }
 
@@ -87,12 +106,23 @@ public class ResourceDefinitionFixtures {
                 DISTRIBUTION_DEFINITION_UUID,
                 "Distribution",
                 "distribution",
-                "http://www.w3.org/ns/dcat#Distribution",
-                "https://www.purl.org/fairtools/fdp/schema/0.1/distributionMetadata",
-                List.of("http://www.w3.org/ns/dcat#Resource", "http://www.w3.org/ns/dcat#Distribution"),
-                null,
-                DATASET_DEFINITION_UUID,
-                null
+                List.of(ShapeFixtures.RESOURCE_SHAPE_UUID, ShapeFixtures.DISTRIBUTION_SHAPE_UUID),
+                List.of(),
+                List.of(
+                        new ResourceDefinitionLink("Access online", "http://www.w3.org/ns/dcat#accessURL"),
+                        new ResourceDefinitionLink("Download", "http://www.w3.org/ns/dcat#downloadURL")
+                )
+        );
+    }
+
+    public ResourceDefinition ontologyDefinition() {
+        return new ResourceDefinition(
+                ONTOLOGY_DEFINITION_UUID,
+                "Ontology",
+                "ontology",
+                List.of(ShapeFixtures.RESOURCE_SHAPE_UUID),
+                List.of(),
+                List.of()
         );
     }
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/shape/data/ShapeFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/shape/data/ShapeFixtures.java
index 039b0be28b9107ee69209bee98fe551f40a44fa9..04a9d3b557717cab1a51fde25193c4bb2e6c8e71 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/shape/data/ShapeFixtures.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/development/shape/data/ShapeFixtures.java
@@ -26,21 +26,35 @@ import nl.dtls.fairdatapoint.entity.shape.Shape;
 import nl.dtls.fairdatapoint.entity.shape.ShapeType;
 import org.springframework.stereotype.Service;
 
+import java.util.Set;
+
 @Service
 public class ShapeFixtures {
 
+    public static final String RESOURCE_SHAPE_UUID = "6a668323-3936-4b53-8380-a4fd2ed082ee";
+
+    public static final String REPOSITORY_SHAPE_UUID = "a92958ab-a414-47e6-8e17-68ba96ba3a2b";
+
+    public static final String CATALOG_SHAPE_UUID = "2aa7ba63-d27a-4c0e-bfa6-3a4e250f4660";
+
+    public static final String DATASET_SHAPE_UUID = "866d7fb8-5982-4215-9c7c-18d0ed1bd5f3";
+
+    public static final String DISTRIBUTION_SHAPE_UUID = "ebacbf83-cd4f-4113-8738-d73c0735b0ab";
+
     public Shape resourceShape() {
         return new Shape(
                 null,
                 "6a668323-3936-4b53-8380-a4fd2ed082ee",
                 "Resource",
+                false,
                 ShapeType.INTERNAL,
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
-                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
                         "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
                         "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                        "@prefix foaf:     <http://xmlns.com/foaf/0.1/>.\n" +
+                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
                         "@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> .\n" +
-                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
                         "\n" +
                         ":ResourceShape a sh:NodeShape ;\n" +
                         "  sh:targetClass dcat:Resource ;\n" +
@@ -56,6 +70,12 @@ public class ShapeFixtures {
                         "    sh:maxCount 1 ;\n" +
                         "    dash:editor dash:TextAreaEditor ;\n" +
                         "  ], [\n" +
+                        "    sh:path dct:publisher ;\n" +
+                        "    sh:node :AgentShape ;\n" +
+                        "    sh:minCount 1 ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:BlankNodeEditor ;\n" +
+                        "  ], [\n" +
                         "    sh:path dct:hasVersion ;\n" +
                         "    sh:name \"version\" ;\n" +
                         "    sh:nodeKind sh:Literal ;\n" +
@@ -64,19 +84,13 @@ public class ShapeFixtures {
                         "    dash:editor dash:TextFieldEditor ;\n" +
                         "    dash:viewer dash:LiteralViewer ;\n" +
                         "  ], [\n" +
-                        "    sh:path dct:license ;\n" +
+                        "    sh:path dct:language ;\n" +
                         "    sh:nodeKind sh:IRI ;\n" +
                         "    sh:maxCount 1 ;\n" +
                         "    dash:editor dash:URIEditor ;\n" +
                         "    dash:viewer dash:LabelViewer ;\n" +
                         "  ], [\n" +
-                        "    sh:path dct:conformsTo ;\n" +
-                        "    sh:name \"specification\" ;\n" +
-                        "    sh:maxCount 1 ;\n" +
-                        "    sh:nodeKind sh:IRI ;\n" +
-                        "    dash:viewer dash:LabelViewer ;\n" +
-                        "  ], [\n" +
-                        "    sh:path dct:language ;\n" +
+                        "    sh:path dct:license ;\n" +
                         "    sh:nodeKind sh:IRI ;\n" +
                         "    sh:maxCount 1 ;\n" +
                         "    dash:editor dash:URIEditor ;\n" +
@@ -85,15 +99,20 @@ public class ShapeFixtures {
                         "    sh:path dct:rights ;\n" +
                         "    sh:nodeKind sh:IRI ;\n" +
                         "    sh:maxCount 1 ;\n" +
-                        "  ], [\n" +
-                        "    sh:path dct:issued ;\n" +
-                        "    sh:datatype xsd:dateTime ;\n" +
-                        "    sh:maxCount 1 ;\n" +
-                        "  ], [\n" +
-                        "    sh:path dct:modified ;\n" +
-                        "    sh:datatype xsd:dateTime ;\n" +
-                        "    sh:maxCount 1 ;\n" +
-                        "  ] ."
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ] .\n" +
+                        "\n" +
+                        ":AgentShape a sh:NodeShape ;\n" +
+                        "  sh:targetClass foaf:Agent ;\n" +
+                        "  sh:property [\n" +
+                        "    sh:path foaf:name;\n" +
+                        "    sh:nodeKind sh:Literal ;\n" +
+                        "    sh:minCount 1 ;\n" +
+                        "    sh:maxCount  1 ;\n" +
+                        "    dash:editor dash:TextFieldEditor ;\n" +
+                        "  ] .",
+                Set.of("http://www.w3.org/ns/dcat#Resource")
         );
     }
 
@@ -102,34 +121,49 @@ public class ShapeFixtures {
                 null,
                 "a92958ab-a414-47e6-8e17-68ba96ba3a2b",
                 "Repository",
+                false,
                 ShapeType.INTERNAL,
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
+                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                        "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                        "@prefix r3d:      <http://www.re3data.org/schema/3-0#> .\n" +
                         "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
                         "@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> .\n" +
-                        "@prefix r3d:      <http://www.re3data.org/schema/3-0#> .\n" +
                         "\n" +
                         ":RepositoryShape a sh:NodeShape ;\n" +
                         "  sh:targetClass r3d:Repository ;\n" +
                         "  sh:property [\n" +
-                        "      sh:path r3d:startDate ;\n" +
-                        "      sh:datatype xsd:date ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ] ,\n" +
-                        "    [\n" +
-                        "      sh:path r3d:lastUpdate ;\n" +
-                        "      sh:datatype xsd:date ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ] ,\n" +
-                        "    [\n" +
-                        "      sh:path r3d:institution ;\n" +
-                        "      sh:class r3d:Institution ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ] ,\n" +
-                        "    [\n" +
-                        "      sh:path r3d:institutionCountry ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ] ."
+                        "    sh:path dct:references ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path r3d:institution ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path r3d:startDate ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:DatePickerEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path r3d:lastUpdate ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:DatePickerEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path r3d:institutionCountry ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ] .\n",
+                Set.of("http://www.w3.org/ns/dcat#Repository")
         );
     }
 
@@ -138,31 +172,39 @@ public class ShapeFixtures {
                 null,
                 "2aa7ba63-d27a-4c0e-bfa6-3a4e250f4660",
                 "Catalog",
+                false,
                 ShapeType.INTERNAL,
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
-                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
                         "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
                         "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
                         "@prefix foaf:     <http://xmlns.com/foaf/0.1/> .\n" +
-                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
                         "\n" +
                         ":CatalogShape a sh:NodeShape ;\n" +
                         "  sh:targetClass dcat:Catalog ;\n" +
                         "  sh:property [\n" +
-                        "      sh:path dct:isPartOf ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:minCount 1 ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path foaf:homePage ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:themeTaxonomy ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      dash:viewer dash:LabelViewer ;\n" +
-                        "    ] ."
+                        "    sh:path dct:issued ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dct:modified ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path foaf:homePage ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:themeTaxonomy ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ] .\n",
+                Set.of("http://www.w3.org/ns/dcat#Catalog")
         );
     }
 
@@ -171,43 +213,53 @@ public class ShapeFixtures {
                 null,
                 "866d7fb8-5982-4215-9c7c-18d0ed1bd5f3",
                 "Dataset",
-                ShapeType.INTERNAL,
+                false,
+                ShapeType.CUSTOM,
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
-                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
                         "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
                         "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
-                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
                         "\n" +
                         ":DatasetShape a sh:NodeShape ;\n" +
                         "  sh:targetClass dcat:Dataset ;\n" +
                         "  sh:property [\n" +
-                        "      sh:path dct:isPartOf ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:minCount 1 ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:landingPage ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:theme ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      dash:editor dash:URIEditor ;\n" +
-                        "      dash:viewer dash:LabelViewer ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:keyword ;\n" +
-                        "      sh:nodeKind sh:Literal ;\n" +
-                        "      dash:editor dash:TextFieldEditor ;\n" +
-                        "      dash:viewer dash:LiteralViewer ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:contactPoint ;\n" +
-                        "       sh:nodeKind sh:IRI ;\n" +
-                        "       sh:maxCount 1 ;\n" +
-                        "    ] ."
+                        "    sh:path dct:issued ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:DatePickerEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dct:modified ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:DatePickerEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ],  [\n" +
+                        "    sh:path dcat:theme ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:minCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:contactPoint ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:keyword ;\n" +
+                        "    sh:nodeKind sh:Literal ;\n" +
+                        "    dash:editor dash:TextFieldEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:landingPage ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "    dash:viewer dash:LabelViewer ;\n" +
+                        "  ] .\n",
+                Set.of("http://www.w3.org/ns/dcat#Dataset")
         );
     }
 
@@ -216,52 +268,59 @@ public class ShapeFixtures {
                 null,
                 "ebacbf83-cd4f-4113-8738-d73c0735b0ab",
                 "Distribution",
-                ShapeType.INTERNAL,
+                false,
+                ShapeType.CUSTOM,
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
-                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
                         "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
                         "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
-                        "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                        "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
                         "\n" +
                         ":DistributionShape a sh:NodeShape ;\n" +
                         "  sh:targetClass dcat:Distribution ;\n" +
                         "  sh:property [\n" +
-                        "      sh:path dct:isPartOf ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:minCount 1 ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:accessURL ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "      dash:editor dash:URIEditor ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:downloadURL ;\n" +
-                        "      sh:nodeKind sh:IRI ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "      dash:editor dash:URIEditor ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:mediaType ;\n" +
-                        "      sh:nodeKind sh:Literal ;\n" +
-                        "      sh:minCount 1 ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "      dash:editor dash:TextFieldEditor ;\n" +
-                        "      dash:viewer dash:LiteralViewer ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:format ;\n" +
-                        "      sh:nodeKind sh:Literal ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "      dash:editor dash:TextFieldEditor ;\n" +
-                        "    ],\n" +
-                        "    [\n" +
-                        "      sh:path dcat:byteSize ;\n" +
-                        "      sh:nodeKind sh:Literal ;\n" +
-                        "      sh:maxCount 1 ;\n" +
-                        "    ] ."
+                        "    sh:path dct:issued ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:DatePickerEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dct:modified ;\n" +
+                        "    sh:datatype xsd:dateTime ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:DatePickerEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:accessURL ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:downloadURL ;\n" +
+                        "    sh:nodeKind sh:IRI ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:URIEditor ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:mediaType ;\n" +
+                        "    sh:nodeKind sh:Literal ;\n" +
+                        "    sh:minCount 1 ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:TextFieldEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:format ;\n" +
+                        "    sh:nodeKind sh:Literal ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:TextFieldEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ], [\n" +
+                        "    sh:path dcat:byteSize ;\n" +
+                        "    sh:nodeKind sh:Literal ;\n" +
+                        "    sh:maxCount 1 ;\n" +
+                        "    dash:editor dash:TextFieldEditor ;\n" +
+                        "    dash:viewer dash:LiteralViewer ;\n" +
+                        "  ] .",
+                Set.of("http://www.w3.org/ns/dcat#Distribution")
         );
     }
 
@@ -270,6 +329,7 @@ public class ShapeFixtures {
                 null,
                 "ceba9984-9838-4be2-a2a7-12213016fd96",
                 "Custom Shape",
+                false,
                 ShapeType.CUSTOM,
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
                         "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
@@ -283,7 +343,8 @@ public class ShapeFixtures {
                         "      sh:nodeKind sh:IRI ;\n" +
                         "      dash:editor dash:URIEditor ;\n" +
                         "      dash:viewer dash:LabelViewer ;\n" +
-                        "    ] ."
+                        "    ] .",
+                Set.of("http://example.org/Dog")
         );
     }
 
@@ -292,6 +353,7 @@ public class ShapeFixtures {
                 null,
                 customShape().getUuid(),
                 customShape().getName(),
+                false,
                 customShape().getType(),
                 "@prefix :         <http://fairdatapoint.org/> .\n" +
                         "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
@@ -311,7 +373,8 @@ public class ShapeFixtures {
                         "      sh:nodeKind sh:Literal ;\n" +
                         "      dash:editor dash:TextFieldEditor ;\n" +
                         "      dash:viewer dash:LiteralViewer ;\n" +
-                        "    ] ."
+                        "    ] .",
+                Set.of("http://example.org/Dog")
         );
     }
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0001_Init.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0001_Init.java
index 00d797bc779c44f2bfa9e1cffb404a060d237c7a..af2742c35d10c9d437f3c61619feba9721f5f55d 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0001_Init.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0001_Init.java
@@ -22,8 +22,8 @@
  */
 package nl.dtls.fairdatapoint.database.mongo.migration.production;
 
-import com.github.mongobee.changeset.ChangeLog;
-import com.github.mongobee.changeset.ChangeSet;
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
 import com.mongodb.client.MongoCollection;
 import com.mongodb.client.MongoDatabase;
 import nl.dtls.fairdatapoint.Profiles;
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0002_Custom_metamodel.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0002_CustomMetamodel.java
old mode 100755
new mode 100644
similarity index 97%
rename from src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0002_Custom_metamodel.java
rename to src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0002_CustomMetamodel.java
index b4dea022bd83e5c2e6af5043a2722fa930c41247..e63858962f98d1fe06555c5b912280119aed1cdf
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0002_Custom_metamodel.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0002_CustomMetamodel.java
@@ -22,8 +22,8 @@
  */
 package nl.dtls.fairdatapoint.database.mongo.migration.production;
 
-import com.github.mongobee.changeset.ChangeLog;
-import com.github.mongobee.changeset.ChangeSet;
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
 import com.mongodb.client.MongoCollection;
 import com.mongodb.client.MongoDatabase;
 import nl.dtls.fairdatapoint.Profiles;
@@ -37,7 +37,7 @@ import static com.mongodb.client.model.Updates.set;
 
 @ChangeLog(order = "0002")
 @Profile(Profiles.PRODUCTION)
-public class Migration_0002_Custom_metamodel {
+public class Migration_0002_CustomMetamodel {
 
     @ChangeSet(order = "0002", id = "0002_Custom_metamodel", author = "migrationBot")
     public void run(MongoDatabase db) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0003_ShapeDefinition.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0003_ShapeDefinition.java
index 0eb865c4445248a13afecab7635cca3532688ad8..e23af882cd03f68f2ecdcd033a3ad8ad2eb94f12 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0003_ShapeDefinition.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0003_ShapeDefinition.java
@@ -22,8 +22,8 @@
  */
 package nl.dtls.fairdatapoint.database.mongo.migration.production;
 
-import com.github.mongobee.changeset.ChangeLog;
-import com.github.mongobee.changeset.ChangeSet;
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
 import com.mongodb.client.MongoCollection;
 import com.mongodb.client.MongoDatabase;
 import nl.dtls.fairdatapoint.Profiles;
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0004_ResourceDefinition.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0004_ResourceDefinition.java
new file mode 100644
index 0000000000000000000000000000000000000000..7db10fd43012884846e88be8eaa7755d92393582
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0004_ResourceDefinition.java
@@ -0,0 +1,202 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.production;
+
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import nl.dtls.fairdatapoint.Profiles;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import org.bson.BasicBSONObject;
+import org.bson.Document;
+import org.bson.types.BasicBSONList;
+import org.springframework.context.annotation.Profile;
+
+import java.util.List;
+
+@ChangeLog(order = "0004")
+@Profile(Profiles.PRODUCTION)
+public class Migration_0004_ResourceDefinition {
+
+    @ChangeSet(order = "0004", id = "Migration_0004_ResourceDefinition", author = "migrationBot")
+    public void run(MongoDatabase db, ResourceDefinitionCache resourceDefinitionCache) {
+        migrateResourceDefinitions(db);
+        resourceDefinitionCache.computeCache();
+    }
+
+    private void migrateResourceDefinitions(MongoDatabase db) {
+        MongoCollection<Document> rdCol = db.getCollection("resourceDefinition");
+        rdCol.deleteMany(new Document());
+        rdCol.insertOne(repositoryDefinition());
+        rdCol.insertOne(catalogDefinition());
+        rdCol.insertOne(datasetDefinition());
+        rdCol.insertOne(distributionDefinition());
+
+        MongoCollection<Document> membershipCol = db.getCollection("membership");
+        membershipCol.deleteMany(new Document());
+        membershipCol.insertOne(membershipOwner());
+        membershipCol.insertOne(membershipDataProvider());
+    }
+
+    private Document repositoryDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "77aaad6a-0136-4c6e-88b9-07ffccd0ee4c");
+        definition.append("name", "Repository");
+        definition.append("urlPrefix", "");
+        definition.append("targetClassUris", List.of("http://www.w3.org/ns/dcat#Resource",
+                "http://www.re3data.org/schema/3-0#Repository"));
+
+        // Child
+        Document child = new Document();
+        child.append("resourceDefinitionUuid", "a0949e72-4466-4d53-8900-9436d1049a4b");
+        child.append("relationUri", "http://www.re3data.org/schema/3-0#dataCatalog");
+        Document listView = new Document();
+        listView.append("title", "Catalogs");
+        listView.append("tagsUri", "http://www.w3.org/ns/dcat#themeTaxonomy");
+        listView.append("metadata", List.of());
+        child.append("listView", listView);
+        definition.append("children", List.of(child));
+
+        // External Links
+        definition.append("externalLinks", List.of());
+
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.resource.ResourceDefinition");
+        return definition;
+    }
+
+    private Document catalogDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "a0949e72-4466-4d53-8900-9436d1049a4b");
+        definition.append("name", "Catalog");
+        definition.append("urlPrefix", "catalog");
+        definition.append("targetClassUris", List.of("http://www.w3.org/ns/dcat#Resource",
+                "http://www.w3.org/ns/dcat#Catalog"));
+
+        // Child
+        Document child = new Document();
+        child.append("resourceDefinitionUuid", "2f08228e-1789-40f8-84cd-28e3288c3604");
+        child.append("relationUri", "http://www.w3.org/ns/dcat#dataset");
+        Document listView = new Document();
+        listView.append("title", "Datasets");
+        listView.append("tagsUri", "http://www.w3.org/ns/dcat#theme");
+        listView.append("metadata", List.of());
+        child.append("listView", listView);
+        definition.append("children", List.of(child));
+
+        // External Links
+        definition.append("externalLinks", List.of());
+
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.resource.ResourceDefinition");
+        return definition;
+    }
+
+    private Document datasetDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "2f08228e-1789-40f8-84cd-28e3288c3604");
+        definition.append("name", "Dataset");
+        definition.append("urlPrefix", "dataset");
+        definition.append("targetClassUris", List.of("http://www.w3.org/ns/dcat#Resource",
+                "http://www.w3.org/ns/dcat#Dataset"));
+
+        // Child
+        Document child = new Document();
+        child.append("resourceDefinitionUuid", "02c649de-c579-43bb-b470-306abdc808c7");
+        child.append("relationUri", "http://www.w3.org/ns/dcat#distribution");
+        // - list View
+        Document listView = new Document();
+        listView.append("title", "Distributions");
+        listView.append("tagsUri", null);
+        // - metadata
+        Document metadata = new Document();
+        metadata.append("title", "Media Type");
+        metadata.append("propertyUri", "http://www.w3.org/ns/dcat#mediaType");
+        listView.append("metadata", List.of(metadata));
+        child.append("listView", listView);
+        definition.append("children", List.of(child));
+
+        // External Links
+        definition.append("externalLinks", List.of());
+
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.resource.ResourceDefinition");
+        return definition;
+    }
+
+    private Document distributionDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "02c649de-c579-43bb-b470-306abdc808c7");
+        definition.append("name", "Distribution");
+        definition.append("urlPrefix", "distribution");
+        definition.append("targetClassUris", List.of("http://www.w3.org/ns/dcat#Resource",
+                "http://www.w3.org/ns/dcat#Distribution"));
+
+        // Child
+        definition.append("children", List.of());
+
+        // External Links
+        Document accessLink = new Document();
+        accessLink.append("title", "Access online");
+        accessLink.append("propertyUri", "http://www.w3.org/ns/dcat#accessURL");
+        Document downloadLink = new Document();
+        downloadLink.append("title", "Download");
+        downloadLink.append("propertyUri", "http://www.w3.org/ns/dcat#downloadURL");
+        definition.append("externalLinks", List.of(accessLink, downloadLink));
+
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.resource.ResourceDefinition");
+        return definition;
+    }
+
+    private Document membershipOwner() {
+        Document user = new Document();
+        user.append("uuid", "49f2bcfd-ef0a-4a3a-a1a3-0fc72a6892a8");
+        user.append("name", "Owner");
+        BasicBSONList permissions = new BasicBSONList();
+        permissions.add(new BasicBSONObject().append("mask", 2).append("code", "W"));
+        permissions.add(new BasicBSONObject().append("mask", 4).append("code", "C"));
+        permissions.add(new BasicBSONObject().append("mask", 8).append("code", "D"));
+        permissions.add(new BasicBSONObject().append("mask", 16).append("code", "A"));
+        user.append("permissions", permissions);
+        BasicBSONList allowedEntities = new BasicBSONList();
+        allowedEntities.add("a0949e72-4466-4d53-8900-9436d1049a4b");
+        allowedEntities.add("2f08228e-1789-40f8-84cd-28e3288c3604");
+        allowedEntities.add("02c649de-c579-43bb-b470-306abdc808c7");
+        user.append("allowedEntities", allowedEntities);
+        user.append("_class", "nl.dtls.fairdatapoint.entity.membership.Membership");
+        return user;
+    }
+
+    private Document membershipDataProvider() {
+        Document user = new Document();
+        user.append("uuid", "87a2d984-7db2-43f6-805c-6b0040afead5");
+        user.append("name", "Data Provider");
+        BasicBSONList permissions = new BasicBSONList();
+        permissions.add(new BasicBSONObject().append("mask", 4).append("code", "C"));
+        user.append("permissions", permissions);
+        BasicBSONList allowedEntities = new BasicBSONList();
+        allowedEntities.add("a0949e72-4466-4d53-8900-9436d1049a4b");
+        user.append("allowedEntities", allowedEntities);
+        user.append("_class", "nl.dtls.fairdatapoint.entity.membership.Membership");
+        return user;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0005_UpdateShapeDefinition.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0005_UpdateShapeDefinition.java
new file mode 100644
index 0000000000000000000000000000000000000000..16655564b28b629d761f8addc24f7ce6ad98e355
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0005_UpdateShapeDefinition.java
@@ -0,0 +1,332 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.production;
+
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import nl.dtls.fairdatapoint.Profiles;
+import org.bson.Document;
+import org.springframework.context.annotation.Profile;
+
+@ChangeLog(order = "0005")
+@Profile(Profiles.PRODUCTION)
+public class Migration_0005_UpdateShapeDefinition {
+
+    @ChangeSet(order = "0005", id = "Migration_0005_UpdateShapeDefinition", author = "migrationBot")
+    public void run(MongoDatabase db) {
+        addShapeDefinitions(db);
+    }
+
+    private void addShapeDefinitions(MongoDatabase db) {
+        MongoCollection<Document> shapeCol = db.getCollection("shape");
+
+        shapeCol.deleteOne(new Document("uuid", "6a668323-3936-4b53-8380-a4fd2ed082ee"));
+        shapeCol.deleteOne(new Document("uuid", "a92958ab-a414-47e6-8e17-68ba96ba3a2b"));
+        shapeCol.deleteOne(new Document("uuid", "2aa7ba63-d27a-4c0e-bfa6-3a4e250f4660"));
+        shapeCol.deleteOne(new Document("uuid", "866d7fb8-5982-4215-9c7c-18d0ed1bd5f3"));
+        shapeCol.deleteOne(new Document("uuid", "ebacbf83-cd4f-4113-8738-d73c0735b0ab"));
+
+        shapeCol.insertOne(resourceDefinition());
+        shapeCol.insertOne(repositoryDefinition());
+        shapeCol.insertOne(catalogDefinition());
+        shapeCol.insertOne(datasetDefinition());
+        shapeCol.insertOne(distributionDefinition());
+    }
+
+    private Document resourceDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "6a668323-3936-4b53-8380-a4fd2ed082ee");
+        definition.append("name", "Resource");
+        definition.append("type", "INTERNAL");
+        definition.append("definition", "@prefix :         <http://fairdatapoint.org/> .\n" +
+                "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
+                "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                "@prefix foaf:     <http://xmlns.com/foaf/0.1/>.\n" +
+                "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                "@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> .\n" +
+                "\n" +
+                ":ResourceShape a sh:NodeShape ;\n" +
+                "  sh:targetClass dcat:Resource ;\n" +
+                "  sh:property [\n" +
+                "    sh:path dct:title ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:minCount 1 ;\n" +
+                "    sh:maxCount  1 ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:description ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:TextAreaEditor ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:publisher ;\n" +
+                "    sh:node :AgentShape ;\n" +
+                "    sh:minCount 1 ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:BlankNodeEditor ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:hasVersion ;\n" +
+                "    sh:name \"version\" ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:minCount 1 ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:language ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:license ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:rights ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ] .\n" +
+                "\n" +
+                ":AgentShape a sh:NodeShape ;\n" +
+                "  sh:targetClass foaf:Agent ;\n" +
+                "  sh:property [\n" +
+                "    sh:path foaf:name;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:minCount 1 ;\n" +
+                "    sh:maxCount  1 ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "  ] .");
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.shape.Shape");
+        return definition;
+    }
+
+
+    private Document repositoryDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "a92958ab-a414-47e6-8e17-68ba96ba3a2b");
+        definition.append("name", "Repository");
+        definition.append("type", "INTERNAL");
+        definition.append("definition", "@prefix :         <http://fairdatapoint.org/> .\n" +
+                "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                "@prefix r3d:      <http://www.re3data.org/schema/3-0#> .\n" +
+                "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                "@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> .\n" +
+                "\n" +
+                ":RepositoryShape a sh:NodeShape ;\n" +
+                "  sh:targetClass r3d:Repository ;\n" +
+                "  sh:property [\n" +
+                "    sh:path dct:references ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path r3d:institution ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path r3d:startDate ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:DatePickerEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path r3d:lastUpdate ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:DatePickerEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path r3d:institutionCountry ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ] .\n");
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.shape.Shape");
+        return definition;
+    }
+
+    private Document catalogDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "2aa7ba63-d27a-4c0e-bfa6-3a4e250f4660");
+        definition.append("name", "Catalog");
+        definition.append("type", "INTERNAL");
+        definition.append("definition", "@prefix :         <http://fairdatapoint.org/> .\n" +
+                "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
+                "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                "@prefix foaf:     <http://xmlns.com/foaf/0.1/> .\n" +
+                "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                "\n" +
+                ":CatalogShape a sh:NodeShape ;\n" +
+                "  sh:targetClass dcat:Catalog ;\n" +
+                "  sh:property [\n" +
+                "    sh:path dct:issued ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:modified ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path foaf:homePage ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:themeTaxonomy ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ] .\n");
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.shape.Shape");
+        return definition;
+    }
+
+    private Document datasetDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "866d7fb8-5982-4215-9c7c-18d0ed1bd5f3");
+        definition.append("name", "Dataset");
+        definition.append("type", "INTERNAL");
+        definition.append("definition", "@prefix :         <http://fairdatapoint.org/> .\n" +
+                "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
+                "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                "\n" +
+                ":DatasetShape a sh:NodeShape ;\n" +
+                "  sh:targetClass dcat:Dataset ;\n" +
+                "  sh:property [\n" +
+                "    sh:path dct:issued ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:DatePickerEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:modified ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:DatePickerEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ],  [\n" +
+                "    sh:path dcat:theme ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:minCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:contactPoint ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:keyword ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:landingPage ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "    dash:viewer dash:LabelViewer ;\n" +
+                "  ] .\n");
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.shape.Shape");
+        return definition;
+    }
+
+    private Document distributionDefinition() {
+        Document definition = new Document();
+        definition.append("uuid", "ebacbf83-cd4f-4113-8738-d73c0735b0ab");
+        definition.append("name", "Distribution");
+        definition.append("type", "INTERNAL");
+        definition.append("definition", "@prefix :         <http://fairdatapoint.org/> .\n" +
+                "@prefix dash:     <http://datashapes.org/dash#> .\n" +
+                "@prefix dcat:     <http://www.w3.org/ns/dcat#> .\n" +
+                "@prefix dct:      <http://purl.org/dc/terms/> .\n" +
+                "@prefix sh:       <http://www.w3.org/ns/shacl#> .\n" +
+                "\n" +
+                ":DistributionShape a sh:NodeShape ;\n" +
+                "  sh:targetClass dcat:Distribution ;\n" +
+                "  sh:property [\n" +
+                "    sh:path dct:issued ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:DatePickerEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dct:modified ;\n" +
+                "    sh:datatype xsd:dateTime ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:DatePickerEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:accessURL ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:downloadURL ;\n" +
+                "    sh:nodeKind sh:IRI ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:URIEditor ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:mediaType ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:minCount 1 ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:format ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ], [\n" +
+                "    sh:path dcat:byteSize ;\n" +
+                "    sh:nodeKind sh:Literal ;\n" +
+                "    sh:maxCount 1 ;\n" +
+                "    dash:editor dash:TextFieldEditor ;\n" +
+                "    dash:viewer dash:LiteralViewer ;\n" +
+                "  ] .");
+        definition.append("_class", "nl.dtls.fairdatapoint.entity.shape.Shape");
+        return definition;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0006_ShapesSharing.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0006_ShapesSharing.java
new file mode 100644
index 0000000000000000000000000000000000000000..45deffc5afb9c5b7304cb7801e3bc459904728db
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0006_ShapesSharing.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.production;
+
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import nl.dtls.fairdatapoint.Profiles;
+import org.bson.Document;
+import org.springframework.context.annotation.Profile;
+
+@ChangeLog(order = "0006")
+@Profile(Profiles.PRODUCTION)
+public class Migration_0006_ShapesSharing {
+
+    @ChangeSet(order = "0006", id = "Migration_0006_ShapesSharing", author = "migrationBot")
+    public void run(MongoDatabase db) {
+        addShapeDefinitions(db);
+    }
+
+    private void addShapeDefinitions(MongoDatabase db) {
+        MongoCollection<Document> shapeCol = db.getCollection("shape");
+        shapeCol.updateMany(
+                Filters.exists("published", false),
+                Updates.set("published", false)
+        );
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0007_RemoveMongobee.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0007_RemoveMongobee.java
new file mode 100644
index 0000000000000000000000000000000000000000..687bae7c32cbcca456c363247ce855ff752db147
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0007_RemoveMongobee.java
@@ -0,0 +1,40 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.production;
+
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import com.mongodb.client.MongoDatabase;
+import nl.dtls.fairdatapoint.Profiles;
+import org.springframework.context.annotation.Profile;
+
+@ChangeLog(order = "0007")
+@Profile(Profiles.PRODUCTION)
+public class Migration_0007_RemoveMongobee {
+
+    @ChangeSet(order = "0007", id = "Migration_0007_RemoveMongobee", author = "migrationBot")
+    public void run(MongoDatabase db) {
+        db.getCollection("dbchangelog").drop();
+        db.getCollection("mongobeelock").drop();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0008_ShapesInternalChange.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0008_ShapesInternalChange.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0bb2f0832669ea7cd33f0ca42bf595d7b38528b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0008_ShapesInternalChange.java
@@ -0,0 +1,57 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.production;
+
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import nl.dtls.fairdatapoint.Profiles;
+import org.bson.Document;
+import org.springframework.context.annotation.Profile;
+
+@ChangeLog(order = "0008")
+@Profile(Profiles.PRODUCTION)
+public class Migration_0008_ShapesInternalChange {
+
+    @ChangeSet(order = "0008", id = "Migration_0008_ShapesInternalChange", author = "migrationBot")
+    public void run(MongoDatabase db) {
+        updateInternalShapesType(db);
+    }
+
+    private void updateInternalShapesType(MongoDatabase db) {
+        MongoCollection<Document> shapeCol = db.getCollection("shape");
+        // DATASET
+        shapeCol.updateOne(
+                Filters.eq("uuid", "866d7fb8-5982-4215-9c7c-18d0ed1bd5f3"),
+                Updates.set("type", "CUSTOM")
+        );
+        // DISTRIBUTION
+        shapeCol.updateOne(
+                Filters.eq("uuid", "ebacbf83-cd4f-4113-8738-d73c0735b0ab"),
+                Updates.set("type", "CUSTOM")
+        );
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0009_ShapeTargetClasses.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0009_ShapeTargetClasses.java
new file mode 100644
index 0000000000000000000000000000000000000000..847ee523b26baaa035e4326f54a426aa84a93893
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/migration/production/Migration_0009_ShapeTargetClasses.java
@@ -0,0 +1,87 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.migration.production;
+
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import nl.dtls.fairdatapoint.Profiles;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionTargetClassesCache;
+import nl.dtls.fairdatapoint.service.shape.ShapeShaclUtils;
+import org.bson.Document;
+import org.springframework.context.annotation.Profile;
+
+import java.util.*;
+
+@ChangeLog(order = "0009")
+@Profile(Profiles.PRODUCTION)
+public class Migration_0009_ShapeTargetClasses {
+
+    @ChangeSet(order = "0009", id = "Migration_0009_ShapeTargetClasses", author = "migrationBot")
+    public void run(MongoDatabase db, ResourceDefinitionCache resourceDefinitionCache, ResourceDefinitionTargetClassesCache targetClassesCache) {
+        updateShapesAndResources(db);
+        resourceDefinitionCache.computeCache();
+        targetClassesCache.computeCache();
+    }
+
+    private void updateShapesAndResources(MongoDatabase db) {
+        Map<String, Set<String>> targetClassesMap = new HashMap<>();
+        // Update shapes
+        MongoCollection<Document> shapeCol = db.getCollection("shape");
+        for (Document document : shapeCol.find()) {
+            String definition = (String) document.get("definition");
+            String uuid = (String) document.get("uuid");
+            Set<String> targetClasses = ShapeShaclUtils.extractTargetClasses(definition);
+            targetClassesMap.put(uuid, targetClasses);
+            shapeCol.updateOne(
+                    Filters.eq("uuid", uuid),
+                    Updates.set("targetClasses", targetClasses)
+            );
+        }
+        // Update resource definitions
+        MongoCollection<Document> rdCol = db.getCollection("resourceDefinition");
+        for (Document document : rdCol.find()) {
+            List<String> targetClassUris = (List<String>) document.get("targetClassUris");
+            Set<String> shapeUuids = new HashSet<>();
+            targetClassUris.forEach(uri -> {
+                targetClassesMap.forEach((shapeUuid, targetClasses) -> {
+                    if (targetClasses.contains(uri)) {
+                        shapeUuids.add(shapeUuid);
+                    }
+                });
+            });
+            rdCol.updateOne(
+                    Filters.eq("uuid", document.get("uuid")),
+                    Updates.unset("targetClassUris")
+            );
+            rdCol.updateOne(
+                    Filters.eq("uuid", document.get("uuid")),
+                    Updates.set("shapeUuids", shapeUuids)
+            );
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ApiKeyRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ApiKeyRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..eef8275d64aae704408258a6b56b3c8dbc3b9859
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ApiKeyRepository.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.apikey.ApiKey;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface ApiKeyRepository extends MongoRepository<ApiKey, String> {
+
+    List<ApiKey> findByUserUuid(String userUuid);
+
+    Optional<ApiKey> findByUuid(String uuid);
+
+    Optional<ApiKey> findByToken(String token);
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/EventRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/EventRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d3b807a4a8a06d58c63bbb7d0f75884d0cb630b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/EventRepository.java
@@ -0,0 +1,44 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.EventType;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.time.Instant;
+import java.util.List;
+
+public interface EventRepository extends MongoRepository<Event, String> {
+
+    List<Event> getAllByType(EventType type);
+
+    List<Event> getAllByFinishedIsNull();
+
+    Page<Event> getAllByRelatedTo(IndexEntry indexEntry, Pageable pageable);
+
+    List<Event> findAllByIncomingPingExchangeRemoteAddrAndCreatedAfter(String remoteAddr, Instant after);
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/IndexEntryRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/IndexEntryRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..3866e4ab0b0b38651653411ebc63176cfbe83a1c
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/IndexEntryRepository.java
@@ -0,0 +1,54 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntryState;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.time.Instant;
+import java.util.Optional;
+
+public interface IndexEntryRepository extends MongoRepository<IndexEntry, String> {
+
+    Optional<IndexEntry> findByUuid(String uuid);
+
+    Optional<IndexEntry> findByClientUrl(String clientUrl);
+
+    Page<IndexEntry> findAllByStateEquals(Pageable pageable, IndexEntryState state);
+
+    Page<IndexEntry> findAllByStateEqualsAndLastRetrievalTimeBefore(Pageable pageable, IndexEntryState state,
+                                                                    Instant when);
+
+    Page<IndexEntry> findAllByStateEqualsAndLastRetrievalTimeAfter(Pageable pageable, IndexEntryState state,
+                                                                   Instant when);
+
+    long countAllByStateEquals(IndexEntryState state);
+
+    long countAllByStateEqualsAndLastRetrievalTimeAfter(IndexEntryState state, Instant when);
+
+    long countAllByStateEqualsAndLastRetrievalTimeBefore(IndexEntryState state, Instant when);
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/IndexSettingsRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/IndexSettingsRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..01b672fb374b8b8e981f70a35dda1dd51297afaf
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/IndexSettingsRepository.java
@@ -0,0 +1,33 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettings;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+public interface IndexSettingsRepository  extends MongoRepository<IndexSettings, String> {
+    Optional<IndexSettings> findFirstBy();
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/MetadataRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/MetadataRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..effc67963b4b8a4722adf06fc2c8ab49f47bf840
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/MetadataRepository.java
@@ -0,0 +1,37 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface MetadataRepository extends MongoRepository<Metadata, String> {
+
+    Optional<Metadata> findByUri(String uri);
+
+    List<Metadata> findByUriIn(List<String> uris);
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ResourceDefinitionRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ResourceDefinitionRepository.java
index b003fbca0bbc987e9600b78e6c78f87bbb2dac57..3336cf1a35dd8c588b348b05ce0435ec2180d584 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ResourceDefinitionRepository.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ResourceDefinitionRepository.java
@@ -25,6 +25,7 @@ package nl.dtls.fairdatapoint.database.mongo.repository;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import org.springframework.data.mongodb.repository.MongoRepository;
 
+import java.util.List;
 import java.util.Optional;
 
 public interface ResourceDefinitionRepository extends MongoRepository<ResourceDefinition, String> {
@@ -33,6 +34,8 @@ public interface ResourceDefinitionRepository extends MongoRepository<ResourceDe
 
     Optional<ResourceDefinition> findByName(String name);
 
-    Optional<ResourceDefinition> findByUriPrefix(String uriPrefix);
+    Optional<ResourceDefinition> findByUrlPrefix(String urlPrefix);
+
+    List<ResourceDefinition> findByShapeUuidsIsContaining(String shapeUuid);
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/SettingsRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/SettingsRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..b0bbb6576f5556585150b34499173fcadf73b888
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/SettingsRepository.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.Optional;
+
+public interface SettingsRepository extends MongoRepository<Settings, String> {
+    Optional<Settings> findFirstBy();
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ShapeRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ShapeRepository.java
index ad4bb006dcc7709dec9f1d50493bb883e0fd1f40..3bf428f92d7e66fd1ca745041bfb8e170b23cb7c 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ShapeRepository.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/ShapeRepository.java
@@ -25,10 +25,13 @@ package nl.dtls.fairdatapoint.database.mongo.repository;
 import nl.dtls.fairdatapoint.entity.shape.Shape;
 import org.springframework.data.mongodb.repository.MongoRepository;
 
+import java.util.List;
 import java.util.Optional;
 
 public interface ShapeRepository extends MongoRepository<Shape, String> {
 
     Optional<Shape> findByUuid(String uuid);
 
+    List<Shape> findAllByPublishedIsTrue();
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/WebhookRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/WebhookRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8331374ac98a1fe5df5985d9fbe812955651300
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/mongo/repository/WebhookRepository.java
@@ -0,0 +1,33 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.mongo.repository;
+
+import nl.dtls.fairdatapoint.entity.index.webhook.Webhook;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface WebhookRepository extends MongoRepository<Webhook, String> {
+    Optional<Webhook> findByUuid(UUID uuid);
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/RdfDevelopmentMigrationRunner.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/RdfDevelopmentMigrationRunner.java
index 86b35da7d6fb185e976935e97767c89694d5f156..f168e4476dc93f8c4d3e9adf2bacce172463260a 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/RdfDevelopmentMigrationRunner.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/RdfDevelopmentMigrationRunner.java
@@ -23,7 +23,7 @@
 package nl.dtls.fairdatapoint.database.rdf.migration.development;
 
 import nl.dtls.fairdatapoint.Profiles;
-import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.MetadataMigration;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.RdfMetadataMigration;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.DependsOn;
 import org.springframework.context.annotation.Profile;
@@ -32,16 +32,16 @@ import org.springframework.stereotype.Service;
 import javax.annotation.PostConstruct;
 
 @Service
-@DependsOn("mongobee")
+@DependsOn("mongockRunner")
 @Profile(Profiles.NON_PRODUCTION)
 public class RdfDevelopmentMigrationRunner {
 
     @Autowired
-    private MetadataMigration metadataMigration;
+    private RdfMetadataMigration rdfMetadataMigration;
 
     @PostConstruct
     public void run() {
-        metadataMigration.runMigration();
+        rdfMetadataMigration.runMigration();
     }
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/MetadataMigration.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/RdfMetadataMigration.java
old mode 100755
new mode 100644
similarity index 69%
rename from src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/MetadataMigration.java
rename to src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/RdfMetadataMigration.java
index fe3f5f294683314e29550feac451e4928433b000..8b9befe40699c25eaae8fa9fdeceeb4e4acc0a74
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/MetadataMigration.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/RdfMetadataMigration.java
@@ -22,13 +22,18 @@
  */
 package nl.dtls.fairdatapoint.database.rdf.migration.development.metadata;
 
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
 import nl.dtls.fairdatapoint.database.common.migration.Migration;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
-import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.data.RdfMetadataFixtures;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepository;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
+import nl.dtls.fairdatapoint.service.metadata.state.MetadataStateService;
 import nl.dtls.fairdatapoint.service.security.MongoAuthenticationService;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
@@ -42,10 +47,13 @@ import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
 @Service
-public class MetadataMigration implements Migration {
+public class RdfMetadataMigration implements Migration {
 
     @Autowired
-    protected MetadataFixtures metadataFixtures;
+    private GenericMetadataRepository metadataRepository;
+
+    @Autowired
+    protected RdfMetadataFixtures rdfMetadataFixtures;
 
     @Autowired
     @Qualifier("catalogMetadataService")
@@ -64,20 +72,26 @@ public class MetadataMigration implements Migration {
     @Autowired
     private MongoAuthenticationService mongoAuthenticationService;
 
+    @Autowired
+    private MetadataStateService metadataStateService;
+
     @Autowired
     @Qualifier("persistentUrl")
     private String persistentUrl;
 
     public void runMigration() {
         try {
-            // 1. Auth user
-            String albertUuid = userFixtures.albert().getUuid();
-            Authentication auth = mongoAuthenticationService.getAuthentication(albertUuid);
+            // 1. Remove all previous metadata
+            metadataRepository.removeAll();
+
+            // 2. Auth user
+            String adminUuid = userFixtures.admin().getUuid();
+            Authentication auth = mongoAuthenticationService.getAuthentication(adminUuid);
             SecurityContextHolder.getContext().setAuthentication(auth);
 
-            // 2. Load metadata fixtures
+            // 3. Load metadata fixtures
             importDefaultFixtures(persistentUrl);
-        } catch (MetadataServiceException e) {
+        } catch (MetadataServiceException | MetadataRepositoryException e) {
             e.printStackTrace();
         }
     }
@@ -88,31 +102,35 @@ public class MetadataMigration implements Migration {
         ResourceDefinition datasetRd = resourceDefinitionFixtures.datasetDefinition();
         ResourceDefinition distributionRd = resourceDefinitionFixtures.distributionDefinition();
 
-        Model repositoryM = metadataFixtures.repositoryMetadata(repositoryUrl);
+        Model repositoryM = rdfMetadataFixtures.repositoryMetadata(repositoryUrl);
         IRI repositoryUri = getUri(repositoryM);
         genericMetadataService.store(repositoryM, i(repositoryUrl), repositoryRd);
+        metadataStateService.modifyState(repositoryUri, new MetaStateChangeDTO(MetadataState.PUBLISHED));
 
-        Model catalog1 = metadataFixtures.catalog1(repositoryUrl, i(repositoryUrl));
+        Model catalog1 = rdfMetadataFixtures.catalog1(repositoryUrl, i(repositoryUrl));
         IRI catalog1Uri = getUri(catalog1);
         catalogMetadataService.store(catalog1, catalog1Uri, catalogRd);
+        metadataStateService.modifyState(catalog1Uri, new MetaStateChangeDTO(MetadataState.PUBLISHED));
 
-        Model catalog2 = metadataFixtures.catalog2(repositoryUrl, repositoryUri);
+        Model catalog2 = rdfMetadataFixtures.catalog2(repositoryUrl, repositoryUri);
         IRI catalog2Uri = getUri(catalog2);
         catalogMetadataService.store(catalog2, catalog2Uri, catalogRd);
 
-        Model dataset1 = metadataFixtures.dataset1(repositoryUrl, catalog1Uri);
+        Model dataset1 = rdfMetadataFixtures.dataset1(repositoryUrl, catalog1Uri);
         IRI dataset1Uri = getUri(dataset1);
         genericMetadataService.store(dataset1, dataset1Uri, datasetRd);
+        metadataStateService.modifyState(dataset1Uri, new MetaStateChangeDTO(MetadataState.PUBLISHED));
 
-        Model dataset2 = metadataFixtures.dataset2(repositoryUrl, catalog1Uri);
+        Model dataset2 = rdfMetadataFixtures.dataset2(repositoryUrl, catalog1Uri);
         IRI dataset2Uri = getUri(dataset2);
         genericMetadataService.store(dataset2, dataset2Uri, datasetRd);
 
-        Model distribution1 = metadataFixtures.distribution1(repositoryUrl, dataset1Uri);
+        Model distribution1 = rdfMetadataFixtures.distribution1(repositoryUrl, dataset1Uri);
         IRI distribution1Uri = getUri(distribution1);
         genericMetadataService.store(distribution1, distribution1Uri, distributionRd);
+        metadataStateService.modifyState(distribution1Uri, new MetaStateChangeDTO(MetadataState.PUBLISHED));
 
-        Model distribution2 = metadataFixtures.distribution2(repositoryUrl, dataset1Uri);
+        Model distribution2 = rdfMetadataFixtures.distribution2(repositoryUrl, dataset1Uri);
         IRI distribution2Uri = getUri(distribution2);
         genericMetadataService.store(distribution2, distribution2Uri, distributionRd);
     }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/data/MetadataFixtures.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/data/RdfMetadataFixtures.java
old mode 100755
new mode 100644
similarity index 90%
rename from src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/data/MetadataFixtures.java
rename to src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/data/RdfMetadataFixtures.java
index b238aaac0c5ad19544f0faeb808b2d96ac467768..d842acaeccd9d0c2813fefb22052ce39583cd480
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/data/MetadataFixtures.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/data/RdfMetadataFixtures.java
@@ -31,16 +31,27 @@ import org.springframework.stereotype.Service;
 import java.util.Arrays;
 
 @Service
-public class MetadataFixtures {
+public class RdfMetadataFixtures {
 
-    @Autowired
     protected MetadataFactory metadataFactory;
 
+    @Autowired
+    public RdfMetadataFixtures(MetadataFactory metadataFactory) {
+        this.metadataFactory = metadataFactory;
+    }
+
     public Model repositoryMetadata(String repositoryUrl) {
         return metadataFactory.createFDPMetadata(
-                "EOSC-Pillar FAIR Data Point",
-                "FAIRDataPoint is a REST API and Web Client for creating, storing, and serving FAIR metadata." +
-                        "Here you can store catalogs, datasets, and distributions.",
+                "My FAIR Data Point",
+                "Duis pellentesque, nunc a fringilla varius, magna dui porta quam, nec ultricies augue turpis sed " +
+                        "velit. Donec id consectetur ligula. Suspendisse pharetra egestas massa, vel varius leo " +
+                        "viverra at. Donec scelerisque id ipsum id semper. Maecenas facilisis augue vel justo " +
+                        "molestie aliquet. Maecenas sed mattis lacus, sed viverra risus. Donec iaculis quis lacus " +
+                        "vitae scelerisque. Nullam fermentum lectus nisi, id vulputate nisi congue nec. Morbi " +
+                        "fermentum justo at justo bibendum, at tempus ipsum tempor. Donec facilisis nibh sed lectus " +
+                        "blandit venenatis. Cras ullamcorper, justo vitae feugiat commodo, orci metus suscipit purus," +
+                        " quis sagittis turpis ante eget ex. Pellentesque malesuada a metus eu pulvinar. Morbi rutrum" +
+                        " euismod eros at varius. Duis finibus dapibus ex, a hendrerit mauris efficitur at.",
                 repositoryUrl
         );
     }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/factory/MetadataFactoryImpl.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/factory/MetadataFactoryImpl.java
index 6bc103abce468f0d958f611d588eaf74ab26173d..30e4885bbe5764f25e66e740aabb2b8395a24739 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/factory/MetadataFactoryImpl.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/development/metadata/factory/MetadataFactoryImpl.java
@@ -22,10 +22,12 @@
  */
 package nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.factory;
 
+import nl.dtls.fairdatapoint.entity.metadata.Agent;
 import nl.dtls.fairdatapoint.util.ValueFactoryHelper;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.model.impl.LinkedHashModel;
+import org.eclipse.rdf4j.model.vocabulary.FOAF;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -89,6 +91,11 @@ public class MetadataFactoryImpl implements MetadataFactory {
         setTitle(metadata, uri, l(title));
         setDescription(metadata, uri, l(description));
         setVersion(metadata, uri, l(1.0f));
+        setPublisher(metadata, uri, new Agent(
+                i("http://example.com/publisher"),
+                i("http://example.com/publisher/mbox"),
+                i(FOAF.AGENT),
+                l("Publisher")));
 
         if (parent != null) {
             setParent(metadata, uri, parent);
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0001_Init.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0001_Init.java
index 7208aeec9f2c55f2e313835222921fd52bff5c5f..b463aaa283d32a3c8f29529258d69cd00ea12705 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0001_Init.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0001_Init.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.database.rdf.migration.production;
 
 import com.mongodb.client.MongoCollection;
 import lombok.extern.slf4j.Slf4j;
-import nl.dtls.fairdatapoint.entity.metadata.Agent;
+import nl.dtls.fairdatapoint.service.reset.FactoryDefaults;
 import nl.dtls.fairdatapoint.vocabulary.DATACITE;
 import nl.dtls.fairdatapoint.vocabulary.FDP;
 import nl.dtls.fairdatapoint.vocabulary.R3D;
@@ -50,7 +50,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.stereotype.Service;
 
-import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -82,13 +82,6 @@ public class Rdf_Migration_0001_Init implements RdfProductionMigration {
     @Autowired
     private IRI language;
 
-    @Autowired
-    private Agent publisher;
-
-    @Autowired
-    @Qualifier("metadataMetrics")
-    private Map<String, String> metadataMetrics;
-
     @Autowired
     private MongoTemplate mongoTemplate;
 
@@ -99,39 +92,12 @@ public class Rdf_Migration_0001_Init implements RdfProductionMigration {
 
     private void createRepositoryInTripleStore() {
         try (RepositoryConnection conn = repository.getConnection()) {
-            List<Statement> s = new ArrayList<>();
-            add(s, RDF.TYPE, R3D.REPOSITORY);
-            add(s, DCTERMS.TITLE, l("EOSC-Pillar FAIR Data Point"));
-            add(s, RDFS.LABEL, l("EOSC-Pillar FAIR Data Point"));
-            add(s, DCTERMS.HAS_VERSION, l(1.0f));
-            add(s, FDP.METADATAISSUED, l(LocalDateTime.now()));
-            add(s, FDP.METADATAMODIFIED, l(LocalDateTime.now()));
-            add(s, DCTERMS.LICENSE, license);
-            add(s, DCTERMS.DESCRIPTION, l("FAIRDataPoint is a REST API and Web Client for creating, storing, and serving FAIR metadata." +
-                    "Here you can store catalogs, datasets, and distributions." ));
-            add(s, DCTERMS.CONFORMS_TO, i("https://www.purl.org/fairtools/fdp/schema/0.1/fdpMetadata"));
-            add(s, DCTERMS.LANGUAGE, language);
-            // Identifier
-            IRI identifierIri = i(persistentUrl + "/#identifier");
-            add(s, DATACITE.HASIDENTIFIER, identifierIri);
-            add(s, identifierIri, RDF.TYPE, DATACITE.IDENTIFIER);
-            add(s, identifierIri, DCTERMS.IDENTIFIER, l(persistentUrl));
-            // Access Rights
-            IRI arIri = i(persistentUrl + "/#accessRights");
-            add(s, DCTERMS.ACCESS_RIGHTS, arIri);
-            add(s, arIri, RDF.TYPE, DCTERMS.RIGHTS_STATEMENT);
-            add(s, arIri, DCTERMS.DESCRIPTION, l(accessRightsDescription));
-            // Publisher
-            add(s, DCTERMS.PUBLISHER, publisher.getUri());
-            add(s, publisher.getUri(), RDF.TYPE, publisher.getType());
-            add(s, publisher.getUri(), FOAF.NAME, publisher.getName());
-            // Metrics
-            metadataMetrics.forEach((metric, metricValue) -> {
-                IRI metUri = i(format("%s/metrics/%s", persistentUrl, DigestUtils.md5Hex(metric)));
-                add(s, Sio.REFERS_TO, metUri);
-                add(s, metUri, Sio.IS_ABOUT, i(metric));
-                add(s, metUri, Sio.REFERS_TO, i(metricValue));
-            });
+            List<Statement> s = FactoryDefaults.repositoryStatements(
+                    persistentUrl,
+                    license,
+                    language,
+                    accessRightsDescription
+            );
             conn.add(s);
         } catch (RepositoryException e) {
             log.error(e.getMessage(), e);
@@ -144,53 +110,7 @@ public class Rdf_Migration_0001_Init implements RdfProductionMigration {
     }
 
     private Document repositoryPermission() {
-        String albertUuid = "7e64818d-6276-46fb-8bb1-732e6e09f7e9";
-        BasicBSONObject owner = new BasicBSONObject().append("name", albertUuid).append("isPrincipal", true);
-        Document acl = new Document();
-        acl.append("className", "nl.dtls.fairdatapoint.entity.metadata.FDPMetadata");
-        acl.append("instanceId", persistentUrl);
-        acl.append("owner", owner);
-        acl.append("inheritPermissions", true);
-        BasicBSONList permissions = new BasicBSONList();
-        permissions.add(
-                new Document()
-                        .append("sid", owner)
-                        .append("permission", 2)
-                        .append("granting", true)
-                        .append("auditFailure", false)
-                        .append("auditSuccess", false));
-        permissions.add(
-                new Document()
-                        .append("sid", owner)
-                        .append("permission", 4)
-                        .append("granting", true)
-                        .append("auditFailure", false)
-                        .append("auditSuccess", false));
-        permissions.add(
-                new Document()
-                        .append("sid", owner)
-                        .append("permission", 8)
-                        .append("granting", true)
-                        .append("auditFailure", false)
-                        .append("auditSuccess", false));
-        permissions.add(
-                new Document()
-                        .append("sid", owner)
-                        .append("permission", 16)
-                        .append("granting", true)
-                        .append("auditFailure", false)
-                        .append("auditSuccess", false));
-        acl.append("permissions", permissions);
-        acl.append("_class", "org.springframework.security.acls.domain.MongoAcl");
-        return acl;
-    }
-
-    private void add(List<Statement> statements, IRI predicate, org.eclipse.rdf4j.model.Value object) {
-        statements.add(s(i(persistentUrl), predicate, object, i(persistentUrl)));
-    }
-
-    private void add(List<Statement> statements, IRI subject, IRI predicate, org.eclipse.rdf4j.model.Value object) {
-        statements.add(s(subject, predicate, object, i(persistentUrl)));
+        return FactoryDefaults.aclRepository(persistentUrl);
     }
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0002_Metadata_Draft.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0002_Metadata_Draft.java
new file mode 100644
index 0000000000000000000000000000000000000000..97aac5a81ae16ad7c0820c406ea35ef903778555
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/migration/production/Rdf_Migration_0002_Metadata_Draft.java
@@ -0,0 +1,67 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.database.rdf.migration.production;
+
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import nl.dtls.rdf.migration.entity.RdfMigrationAnnotation;
+import nl.dtls.rdf.migration.runner.RdfProductionMigration;
+import org.eclipse.rdf4j.common.iteration.Iterations;
+import org.eclipse.rdf4j.repository.Repository;
+import org.eclipse.rdf4j.repository.RepositoryConnection;
+import org.eclipse.rdf4j.repository.RepositoryException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@RdfMigrationAnnotation(
+        number = 2,
+        name = "Metadata Draft",
+        description = "Support metadata in DRAFT state")
+@Slf4j
+@Service
+public class Rdf_Migration_0002_Metadata_Draft implements RdfProductionMigration {
+
+    @Autowired
+    protected Repository repository;
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    public void runMigration() {
+        createRepositoryInTripleStore();
+    }
+
+    private void createRepositoryInTripleStore() {
+        try (RepositoryConnection conn = repository.getConnection()) {
+            Iterations.asList(conn.getContextIDs())
+                    .forEach(iri ->
+                            metadataRepository.save(new Metadata(null, iri.stringValue(), MetadataState.PUBLISHED))
+                    );
+        } catch (RepositoryException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/AbstractMetadataRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/AbstractMetadataRepository.java
index 41d06a167786b830de8709d60aaaed43f3977081..e5fad355f8bd8dce3efa36005bd96a7d8744abbb 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/AbstractMetadataRepository.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/AbstractMetadataRepository.java
@@ -23,86 +23,131 @@
 package nl.dtls.fairdatapoint.database.rdf.repository.common;
 
 import com.google.common.base.Charsets;
-import com.google.common.base.Preconditions;
 import com.google.common.io.Resources;
 import lombok.extern.slf4j.Slf4j;
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.entity.search.SearchResult;
+import nl.dtls.fairdatapoint.entity.search.SearchResultRelation;
 import org.eclipse.rdf4j.common.iteration.Iterations;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Resource;
-import org.eclipse.rdf4j.model.Statement;
-import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.*;
 import org.eclipse.rdf4j.query.BindingSet;
 import org.eclipse.rdf4j.query.QueryResults;
 import org.eclipse.rdf4j.query.TupleQuery;
 import org.eclipse.rdf4j.repository.Repository;
 import org.eclipse.rdf4j.repository.RepositoryConnection;
 import org.eclipse.rdf4j.repository.RepositoryException;
-import org.eclipse.rdf4j.repository.RepositoryResult;
 import org.springframework.beans.factory.annotation.Autowired;
 
-import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.net.URL;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
 
 @Slf4j
 public abstract class AbstractMetadataRepository {
 
+    private static final String FIND_ENTITY_BY_LITERAL = "findEntityByLiteral.sparql";
+    private static final String FIND_CHILD_TITLES = "findChildTitles.sparql";
+
     @Autowired
     protected Repository repository;
 
-    public List<Statement> retrieveResource(@Nonnull IRI uri) throws MetadataRepositoryException {
-        Preconditions.checkNotNull(uri, "URI must not be null.");
-        log.info("Get statements for the URI {}", uri.toString());
+    public List<Resource> findResources() throws MetadataRepositoryException {
+        try (RepositoryConnection conn = repository.getConnection()) {
+
+            return Iterations.asList(
+                    conn.getContextIDs()
+            );
+        } catch (RepositoryException e) {
+            throw new MetadataRepositoryException("Error retrieve resource :" + e.getMessage());
+        }
+    }
 
+    public List<Statement> find(IRI context) throws MetadataRepositoryException {
         try (RepositoryConnection conn = repository.getConnection()) {
-            log.info("Connexion to repository hé", conn);
-            RepositoryResult<Statement> queryResult = conn.getStatements(null, null, null, uri);
-            log.info("Query result 2", queryResult);
-            return Iterations.asList(queryResult);
+            return Iterations.asList(
+                    conn.getStatements(null, null, null, context)
+            );
         } catch (RepositoryException e) {
             throw new MetadataRepositoryException("Error retrieve resource :" + e.getMessage());
         }
     }
 
-    public boolean isStatementExist(Resource rsrc, IRI pred, Value value) throws MetadataRepositoryException {
+    public List<SearchResult> findByLiteral(Literal query) throws MetadataRepositoryException {
+        return runSparqlQuery(FIND_ENTITY_BY_LITERAL, AbstractMetadataRepository.class, Map.of(
+                "query", query))
+                .stream()
+                .map(s -> new SearchResult(
+                                s.getValue("entity").stringValue(),
+                                s.getValue("rdfType").stringValue(),
+                                s.getValue("title").stringValue(),
+                                ofNullable(s.getValue("description")).map(Value::stringValue).orElse(""),
+                                new SearchResultRelation(
+                                        s.getValue("relationPredicate").stringValue(),
+                                        s.getValue("relationObject").stringValue())
+                        )
+                )
+                .collect(toList());
+    }
+
+    public Map<String, String> findChildTitles(IRI parent, IRI relation) throws MetadataRepositoryException {
+        Map<String, String> titles = new HashMap<>();
+
+        var results = runSparqlQuery(FIND_CHILD_TITLES, AbstractMetadataRepository.class, Map.of(
+            "parent", parent,
+            "relation", relation
+        ));
+
+        for (var result : results) {
+            var childUri = result.getValue("child").stringValue();
+            var title = result.getValue("title").stringValue();
+            titles.put(childUri, title);
+        }
+
+        return titles;
+    }
+
+    public boolean checkExistence(Resource subject, IRI predicate, Value object) throws MetadataRepositoryException {
         try (RepositoryConnection conn = repository.getConnection()) {
-            log.info("Check if statements exists");
-            return conn.hasStatement(rsrc, pred, value, false);
+            return conn.hasStatement(subject, predicate, object, false);
         } catch (RepositoryException e) {
             throw new MetadataRepositoryException("Error check statement existence :" + e.getMessage());
         }
     }
 
-    public void storeStatements(List<Statement> statements, IRI... cntx) throws MetadataRepositoryException {
+    public void save(List<Statement> statements, IRI context) throws MetadataRepositoryException {
         try (RepositoryConnection conn = repository.getConnection()) {
-            if (cntx != null) {
-                conn.add(statements, cntx);
-            } else {
-                conn.add(statements);
-            }
-
+            conn.add(statements, context);
         } catch (RepositoryException e) {
             throw new MetadataRepositoryException("Error storing statements :" + e.getMessage());
         }
     }
 
-    public void removeStatement(Resource rsrc, IRI pred, Value value, IRI... contexts) throws MetadataRepositoryException {
+    public void removeAll() throws MetadataRepositoryException {
         try (RepositoryConnection conn = repository.getConnection()) {
-            conn.remove(rsrc, pred, value, contexts);
+            conn.clear();
         } catch (RepositoryException e) {
-            throw (new MetadataRepositoryException("Error removing statement"));
+            throw new MetadataRepositoryException("Error remove all :" + e.getMessage());
         }
     }
 
-    public void removeResource(IRI uri) throws MetadataRepositoryException {
+    public void remove(IRI uri) throws MetadataRepositoryException {
         removeStatement(null, null, null, uri);
     }
 
+    public void removeStatement(Resource subject, IRI predicate, Value object, IRI context) throws MetadataRepositoryException {
+        try (RepositoryConnection conn = repository.getConnection()) {
+            conn.remove(subject, predicate, object, context);
+        } catch (RepositoryException e) {
+            throw (new MetadataRepositoryException("Error removing statement"));
+        }
+    }
+
     public List<BindingSet> runSparqlQuery(String queryName, Class repositoryType, Map<String, Value> bindings) throws MetadataRepositoryException {
         try (RepositoryConnection conn = repository.getConnection()) {
             String queryString = loadSparqlQuery(queryName, repositoryType);
@@ -121,6 +166,4 @@ public abstract class AbstractMetadataRepository {
         URL fileURL = repositoryType.getResource(queryName);
         return Resources.toString(fileURL, Charsets.UTF_8);
     }
-
-
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepository.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepository.java
index 52dc525ae1c05c3b5a30812f8d20b9a20d4911a6..691a48e0e19b621f24da674930fe1f1a59389f46 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepository.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepository.java
@@ -28,10 +28,8 @@
 package nl.dtls.fairdatapoint.database.rdf.repository.common;
 
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Resource;
-import org.eclipse.rdf4j.model.Statement;
-import org.eclipse.rdf4j.model.Value;
+import nl.dtls.fairdatapoint.entity.search.SearchResult;
+import org.eclipse.rdf4j.model.*;
 import org.eclipse.rdf4j.query.BindingSet;
 
 import java.util.List;
@@ -39,15 +37,23 @@ import java.util.Map;
 
 public interface MetadataRepository {
 
-    List<Statement> retrieveResource(IRI uri) throws MetadataRepositoryException;
+    List<Resource> findResources() throws MetadataRepositoryException;
 
-    void storeStatements(List<Statement> statements, IRI... iri) throws MetadataRepositoryException;
+    List<Statement> find(IRI context) throws MetadataRepositoryException;
 
-    void removeStatement(Resource rsrc, IRI uri, Value value, IRI... contexts) throws MetadataRepositoryException;
+    List<SearchResult> findByLiteral(Literal query) throws MetadataRepositoryException;
 
-    boolean isStatementExist(Resource rsrc, IRI pred, Value value) throws MetadataRepositoryException;
+    Map<String, String> findChildTitles(IRI parent, IRI relation) throws MetadataRepositoryException;
 
-    void removeResource(IRI uri) throws MetadataRepositoryException;
+    boolean checkExistence(Resource subject, IRI predicate, Value object) throws MetadataRepositoryException;
+
+    void save(List<Statement> statements, IRI context) throws MetadataRepositoryException;
+
+    void removeAll() throws MetadataRepositoryException;
+
+    void remove(IRI uri) throws MetadataRepositoryException;
+
+    void removeStatement(Resource subject, IRI predicate, Value object, IRI context) throws MetadataRepositoryException;
 
     List<BindingSet> runSparqlQuery(String queryName, Class repositoryType, Map<String, Value> bindings) throws MetadataRepositoryException;
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryImpl.java b/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryImpl.java
index 5b754625fa4df3d5d1975c875f88e98b74ef31ef..ab343bcb09af0743284d0ce960a41e559243391c 100755
--- a/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryImpl.java
+++ b/src/main/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryImpl.java
@@ -29,18 +29,16 @@ package nl.dtls.fairdatapoint.database.rdf.repository.generic;
 
 import nl.dtls.fairdatapoint.database.rdf.repository.common.AbstractMetadataRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Resource;
-import org.eclipse.rdf4j.model.Statement;
-import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.*;
+import org.eclipse.rdf4j.model.impl.LinkedHashModel;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.Cache;
 import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
 
 import static nl.dtls.fairdatapoint.config.CacheConfig.CATALOG_THEMES_CACHE;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getParent;
 
 @Service("genericMetadataRepository")
 public class GenericMetadataRepositoryImpl extends AbstractMetadataRepository implements GenericMetadataRepository {
@@ -49,29 +47,30 @@ public class GenericMetadataRepositoryImpl extends AbstractMetadataRepository im
     private ConcurrentMapCacheManager cacheManager;
 
     @Override
-    public void storeStatements(List<Statement> statements, IRI... cntx) throws MetadataRepositoryException {
-        super.storeStatements(statements, cntx);
-        for (IRI uri : cntx) {
-            cache().evict(uri.toString());
-        }
+    public void save(List<Statement> statements, IRI context) throws MetadataRepositoryException {
+        super.save(statements, context);
+        clearCatalogCache(context);
     }
 
     @Override
-    public void removeStatement(Resource rsrc, IRI pred, Value value, IRI... contexts) throws MetadataRepositoryException {
-        super.removeStatement(rsrc, pred, value, contexts);
-        if (pred != null) {
-            cache().evict(pred.toString());
-        }
+    public void remove(IRI uri) throws MetadataRepositoryException {
+        clearCatalogCache(uri);
+        super.remove(uri);
     }
 
     @Override
-    public void removeResource(IRI uri) throws MetadataRepositoryException {
-        super.removeResource(uri);
-        cache().evict(uri.toString());
+    public void removeStatement(Resource subject, IRI predicate, Value object, IRI context) throws MetadataRepositoryException {
+        clearCatalogCache(context);
+        super.removeStatement(subject, predicate, object, context);
     }
 
-    private Cache cache() {
-        return cacheManager.getCache(CATALOG_THEMES_CACHE);
+    private void clearCatalogCache(IRI uri) throws MetadataRepositoryException {
+        Model metadata = new LinkedHashModel();
+        metadata.addAll(find(uri));
+        IRI parent = getParent(metadata);
+        if (parent != null) {
+            cacheManager.getCache(CATALOG_THEMES_CACHE).evict(parent.stringValue());
+        }
     }
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/apikey/ApiKey.java b/src/main/java/nl/dtls/fairdatapoint/entity/apikey/ApiKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..58285ca222a7796de46291379c1060bd010da323
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/apikey/ApiKey.java
@@ -0,0 +1,47 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.apikey;
+
+import lombok.*;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Document
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder(toBuilder = true)
+public class ApiKey {
+
+    @Id
+    protected ObjectId id;
+
+    private String uuid;
+
+    private String userUuid;
+
+    private String token;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/exception/FeatureDisabledException.java b/src/main/java/nl/dtls/fairdatapoint/entity/exception/FeatureDisabledException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8e678965ee7b04f9971f0f3a66b4d4244ccf75a
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/exception/FeatureDisabledException.java
@@ -0,0 +1,35 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class FeatureDisabledException extends RuntimeException {
+
+    public FeatureDisabledException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/exception/RdfValidationException.java b/src/main/java/nl/dtls/fairdatapoint/entity/exception/RdfValidationException.java
index 46283e20097990afa87ff507da0da6fee2438fca..82d2b4037732d8c743e762e17ac6fc569d75c7ab 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/exception/RdfValidationException.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/exception/RdfValidationException.java
@@ -33,6 +33,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
 @AllArgsConstructor
 public class RdfValidationException extends RuntimeException {
 
-    private Model model;
+    private final Model model;
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/exception/ShapeImportException.java b/src/main/java/nl/dtls/fairdatapoint/entity/exception/ShapeImportException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f70b560e697f8adcd18c047436887e1ac66f6c5
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/exception/ShapeImportException.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+@Getter
+@AllArgsConstructor
+public class ShapeImportException extends RuntimeException {
+
+    private final String from;
+
+    private final String message;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/IndexEntry.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/IndexEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca5cead25e207403bf0c702656d5e47a9d599db4
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/IndexEntry.java
@@ -0,0 +1,79 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.entry;
+
+import lombok.*;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.Duration;
+import java.time.Instant;
+
+@Document
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class IndexEntry {
+
+    @Id
+    protected ObjectId id;
+
+    private String uuid;
+
+    private String clientUrl;
+
+    private IndexEntryState state = IndexEntryState.Unknown;
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Instant registrationTime;
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Instant modificationTime;
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Instant lastRetrievalTime;
+
+    private RepositoryMetadata currentMetadata;
+
+    public IndexEntry(String uuid, String clientUrl, IndexEntryState state, Instant registrationTime,
+                      Instant modificationTime, Instant lastRetrievalTime, RepositoryMetadata currentMetadata) {
+        this.uuid = uuid;
+        this.clientUrl = clientUrl;
+        this.state = state;
+        this.registrationTime = registrationTime;
+        this.modificationTime = modificationTime;
+        this.lastRetrievalTime = lastRetrievalTime;
+        this.currentMetadata = currentMetadata;
+    }
+
+    public Duration getLastRetrievalAgo() {
+        if (lastRetrievalTime == null) {
+            return null;
+        }
+        return Duration.between(lastRetrievalTime, Instant.now());
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/IndexEntryState.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/IndexEntryState.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5db38f2f7a803f290c00a505bb7a578046bebd5
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/IndexEntryState.java
@@ -0,0 +1,35 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.entry;
+
+public enum IndexEntryState {
+
+    Unknown,
+
+    Valid, // Active / Inactive based on timestamps
+
+    Unreachable,
+
+    Invalid
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/RepositoryMetadata.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/RepositoryMetadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1139ae818eb037f69703961178addf91f539457
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/entry/RepositoryMetadata.java
@@ -0,0 +1,48 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.entry;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.HashMap;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class RepositoryMetadata {
+
+    public static final Integer CURRENT_VERSION = 1;
+
+    private Integer metadataVersion = CURRENT_VERSION;
+
+    private String repositoryUri;
+
+    @NotNull
+    private HashMap<String, String> metadata = new HashMap<>();
+
+}
+
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/AdminTrigger.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/AdminTrigger.java
new file mode 100644
index 0000000000000000000000000000000000000000..839d3e309168007ed37c96df6f8e1b105657e383
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/AdminTrigger.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AdminTrigger {
+    private String remoteAddr;
+    private String tokenName;
+    private String clientUrl;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/Event.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/Event.java
new file mode 100644
index 0000000000000000000000000000000000000000..3443a8ab23f4fbb0f47c1a4a4792374735b4a241
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/Event.java
@@ -0,0 +1,124 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Document(collection = "event")
+public class Event {
+    @Id
+    protected ObjectId id;
+    @NotNull
+    private UUID uuid = UUID.randomUUID();
+    @NotNull
+    private EventType type;
+    @NotNull
+    private Integer version;
+
+    @DBRef
+    private Event triggeredBy;
+    @DBRef
+    private IndexEntry relatedTo;
+
+    // Content (one of those)
+    private IncomingPing incomingPing;
+    private MetadataRetrieval metadataRetrieval;
+    private AdminTrigger adminTrigger;
+    private WebhookPing webhookPing;
+    private WebhookTrigger webhookTrigger;
+
+    @NotNull
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Instant created = Instant.now();
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Instant executed;
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Instant finished;
+
+    public boolean isExecuted() {
+        return executed != null;
+    }
+
+    public void execute() {
+        executed = Instant.now();
+    }
+
+    public boolean isFinished() {
+        return finished != null;
+    }
+
+    public void finish() {
+        finished = Instant.now();
+    }
+
+    public Event(Integer version, IncomingPing incomingPing) {
+        this.type = EventType.IncomingPing;
+        this.version = version;
+        this.incomingPing = incomingPing;
+    }
+
+    public Event(Integer version, Event triggerEvent, IndexEntry relatedTo, MetadataRetrieval metadataRetrieval) {
+        this.type = EventType.MetadataRetrieval;
+        this.version = version;
+        this.triggeredBy = triggerEvent;
+        this.relatedTo = relatedTo;
+        this.metadataRetrieval = metadataRetrieval;
+    }
+
+    public Event(Integer version, AdminTrigger adminTrigger) {
+        this.type = EventType.AdminTrigger;
+        this.version = version;
+        this.adminTrigger = adminTrigger;
+    }
+
+    public Event(Integer version, WebhookTrigger webhookTrigger, Event triggerEvent) {
+        this.type = EventType.WebhookTrigger;
+        this.version = version;
+        this.webhookTrigger = webhookTrigger;
+        this.triggeredBy = triggerEvent;
+        this.relatedTo = triggerEvent.getRelatedTo();
+    }
+
+    public Event(Integer version, WebhookPing webhookPing) {
+        this.type = EventType.WebhookPing;
+        this.version = version;
+        this.webhookPing = webhookPing;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/EventType.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/EventType.java
new file mode 100644
index 0000000000000000000000000000000000000000..54406f972a0738194290f8c01e74ffeef588095e
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/EventType.java
@@ -0,0 +1,31 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+public enum EventType {
+    AdminTrigger,
+    MetadataRetrieval,
+    WebhookTrigger,
+    IncomingPing,
+    WebhookPing,
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/IncomingPing.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/IncomingPing.java
new file mode 100644
index 0000000000000000000000000000000000000000..118c3c8e28b0f149bad8eff440cab47af5731506
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/IncomingPing.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IncomingPing {
+    private Exchange exchange;
+    private Boolean newEntry;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/MetadataRetrieval.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/MetadataRetrieval.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c65cbac27180ff7776606723a13285336ea3958
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/MetadataRetrieval.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.dtls.fairdatapoint.entity.index.entry.RepositoryMetadata;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MetadataRetrieval {
+    private String error;
+    private Exchange exchange;
+    private RepositoryMetadata metadata;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/WebhookPing.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/WebhookPing.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0776fe735441e5c059b29d005dd3ab05dae3f65
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/WebhookPing.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class WebhookPing {
+    private String remoteAddr;
+    private String tokenName;
+    private UUID webhookUuid;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/event/WebhookTrigger.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/WebhookTrigger.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c4d516734255fa2a44115849a3ae285a373d911
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/event/WebhookTrigger.java
@@ -0,0 +1,43 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+import nl.dtls.fairdatapoint.entity.index.webhook.Webhook;
+import nl.dtls.fairdatapoint.entity.index.webhook.WebhookEvent;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class WebhookTrigger {
+    @DBRef
+    private Webhook webhook;
+
+    private WebhookEvent matchedEvent;
+
+    private Exchange exchange;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/IncorrectPingFormatException.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/IncorrectPingFormatException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5dec39b4da8d1f4176e2894dee811d60ba463c6a
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/IncorrectPingFormatException.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.exception;
+
+import org.springframework.http.HttpStatus;
+
+public class IncorrectPingFormatException extends IndexException {
+
+    public IncorrectPingFormatException(String message) {
+        super(message, HttpStatus.BAD_REQUEST);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/IndexException.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/IndexException.java
new file mode 100644
index 0000000000000000000000000000000000000000..bdfcb2932859208d74dcbfaf6ccd5eae5e88a394
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/IndexException.java
@@ -0,0 +1,44 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.exception;
+
+import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
+import org.springframework.http.HttpStatus;
+
+public abstract class IndexException extends RuntimeException {
+
+    protected final HttpStatus status;
+
+    public IndexException(String message, HttpStatus status) {
+        super(message);
+        this.status = status;
+    }
+
+    public HttpStatus getStatus() {
+        return status;
+    }
+
+    public ErrorDTO getErrorDTO() {
+        return new ErrorDTO(getStatus(), getMessage());
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/PingDeniedException.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/PingDeniedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fd6f1d0ea68689c1579a7348248875bdac3d59d
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/PingDeniedException.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.exception;
+
+import org.springframework.http.HttpStatus;
+
+public class PingDeniedException extends IndexException {
+
+    public PingDeniedException(String clientUrl) {
+        super("Client URL is denied: " + clientUrl, HttpStatus.FORBIDDEN);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/RateLimitException.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/RateLimitException.java
new file mode 100644
index 0000000000000000000000000000000000000000..923ccb9f78a8332fdef3582bfc36ced0d1c3d0f1
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/exception/RateLimitException.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.exception;
+
+import org.springframework.http.HttpStatus;
+
+public class RateLimitException extends IndexException {
+
+    public RateLimitException(String message) {
+        super(message, HttpStatus.TOO_MANY_REQUESTS);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Exchange.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Exchange.java
new file mode 100644
index 0000000000000000000000000000000000000000..a6505399aa94cc9db449d8cb9850fe89eae5e31f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Exchange.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.http;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Exchange {
+    private ExchangeDirection direction;
+    private ExchangeState state = ExchangeState.Prepared;
+    private String remoteAddr;
+    private String error;
+
+    private Request request = new Request();
+    private Response response = new Response();
+
+    public Exchange(ExchangeDirection direction, String remoteAddr) {
+        this.direction = direction;
+        this.remoteAddr = remoteAddr;
+    }
+
+    public Exchange(ExchangeDirection direction) {
+        this.direction = direction;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/membership/MembershipEntity.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/ExchangeDirection.java
old mode 100755
new mode 100644
similarity index 90%
rename from src/main/java/nl/dtls/fairdatapoint/entity/membership/MembershipEntity.java
rename to src/main/java/nl/dtls/fairdatapoint/entity/index/http/ExchangeDirection.java
index d6ce75767da26326fab53eb064cf9700dcc3dc00..14bebd589823b751ab4cdcfe01d572c90259ead5
--- a/src/main/java/nl/dtls/fairdatapoint/entity/membership/MembershipEntity.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/ExchangeDirection.java
@@ -20,8 +20,9 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-package nl.dtls.fairdatapoint.entity.membership;
+package nl.dtls.fairdatapoint.entity.index.http;
 
-public enum MembershipEntity {
-    CATALOG, DATASET, DISTRIBUTION
+public enum ExchangeDirection {
+    INCOMING,
+    OUTGOING
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/http/ExchangeState.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/ExchangeState.java
new file mode 100644
index 0000000000000000000000000000000000000000..733c676e8149ffbcb3af4bfd405f7db9417f5513
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/ExchangeState.java
@@ -0,0 +1,31 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.http;
+
+public enum ExchangeState {
+    Prepared,
+    Requested,
+    Timeout,
+    Failed,
+    Retrieved
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Request.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Request.java
new file mode 100644
index 0000000000000000000000000000000000000000..e662148c448c0b8383f1abc1cb21fbdeed4c1da8
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Request.java
@@ -0,0 +1,77 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.http;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.http.HttpEntity;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.http.HttpRequest;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Request {
+    private String method;
+    private String url;
+
+    private Map<String, List<String>> headers;
+    private String body;
+
+    public Map<String, List<String>> getHeaders() {
+        return headers;
+    }
+
+    public void setHeaders(Map<String, List<String>> headers) {
+        this.headers = headers;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+    public void setFromHttpEntity(HttpEntity<String> httpEntity) {
+        body = httpEntity.getBody();
+        headers = httpEntity.getHeaders();
+    }
+
+    public void setFromHttpServletRequest(HttpServletRequest request) {
+        method = request.getMethod();
+        url = request.getRequestURI();
+    }
+
+    public void setFromHttpRequest(HttpRequest request) {
+        method = request.method();
+        url = request.uri().toString();
+        body = request.bodyPublisher().map(Object::toString).orElse(null);
+        headers = request.headers().map();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Response.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Response.java
new file mode 100644
index 0000000000000000000000000000000000000000..719597c53cdc440c0c2197275a9ae630354c6b37
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/http/Response.java
@@ -0,0 +1,50 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.http;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.net.http.HttpResponse;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Response {
+    private Integer code;
+    private String url;
+    private String origin;
+
+    private Map<String, List<String>> headers;
+    private String body;
+
+    public void setFromHttpResponse(HttpResponse<String> response) {
+        code = response.statusCode();
+        url = response.uri().toString();
+        headers = response.headers().map();
+        body = response.body();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettings.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3227bce699ec9720b3ef914437e847588ff227e
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettings.java
@@ -0,0 +1,68 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.settings;
+
+import lombok.*;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import javax.validation.constraints.NotNull;
+import java.util.Objects;
+
+@Document
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class IndexSettings {
+    @Id
+    protected ObjectId id;
+
+    @NotNull
+    private IndexSettingsRetrieval retrieval;
+
+    @NotNull
+    private IndexSettingsPing ping;
+
+    public static IndexSettings getDefault() {
+        IndexSettings settings = new IndexSettings();
+        settings.setPing(IndexSettingsPing.getDefault());
+        settings.setRetrieval(IndexSettingsRetrieval.getDefault());
+        return settings;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        IndexSettings that = (IndexSettings) o;
+        return retrieval.equals(that.retrieval) && ping.equals(that.ping);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(retrieval, ping);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettingsPing.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettingsPing.java
new file mode 100644
index 0000000000000000000000000000000000000000..32370ee23bdb0ab9bcf053d64f149d4a7a735fec
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettingsPing.java
@@ -0,0 +1,59 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.settings;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+@EqualsAndHashCode
+public class IndexSettingsPing {
+    @NotNull
+    private Duration validDuration;
+
+    @NotNull
+    private Duration rateLimitDuration;
+
+    @NotNull
+    private Integer rateLimitHits;
+
+    @NotNull
+    private List<String> denyList;
+
+    public static IndexSettingsPing getDefault() {
+        IndexSettingsPing ping = new IndexSettingsPing();
+        ping.setValidDuration(Duration.ofDays(7));
+        ping.setRateLimitDuration(Duration.ofHours(6));
+        ping.setRateLimitHits(10);
+        ping.setDenyList(Collections.singletonList("^(http|https)://localhost(:[0-9]+){0,1}.*$"));
+        return ping;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettingsRetrieval.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettingsRetrieval.java
new file mode 100644
index 0000000000000000000000000000000000000000..3aff145ca4f0aa1fe6f37bf2813c8a2bee80683c
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/settings/IndexSettingsRetrieval.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.settings;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+@EqualsAndHashCode
+public class IndexSettingsRetrieval {
+    @NotNull
+    private Duration rateLimitWait;
+
+    @NotNull
+    private Duration timeout;
+
+    public static IndexSettingsRetrieval getDefault() {
+        IndexSettingsRetrieval retrieval = new IndexSettingsRetrieval();
+        retrieval.setRateLimitWait(Duration.ofMinutes(10));
+        retrieval.setTimeout(Duration.ofMinutes(1));
+        return retrieval;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/webhook/Webhook.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/webhook/Webhook.java
new file mode 100644
index 0000000000000000000000000000000000000000..8721c74bc7e47c040429bf9c3d6e2a1a0e191814
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/webhook/Webhook.java
@@ -0,0 +1,61 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.webhook;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Document(collection = "webhook")
+public class Webhook {
+    @Id
+    protected ObjectId id;
+
+    @NotNull
+    private UUID uuid = UUID.randomUUID();
+
+    private String payloadUrl;
+
+    private String secret;
+
+    private boolean allEvents;
+
+    private List<WebhookEvent> events = new ArrayList<>();
+
+    private boolean allEntries;
+
+    private List<String> entries = new ArrayList<>();
+
+    private boolean enabled;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/index/webhook/WebhookEvent.java b/src/main/java/nl/dtls/fairdatapoint/entity/index/webhook/WebhookEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4d22fca83099282a78d6c8c6592b5c158e0828e
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/index/webhook/WebhookEvent.java
@@ -0,0 +1,33 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.index.webhook;
+
+public enum WebhookEvent {
+    NewEntry,
+    IncomingPing,
+    EntryValid,
+    EntryInvalid,
+    EntryUnreachable,
+    AdminTrigger,
+    WebhookPing
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/membership/Membership.java b/src/main/java/nl/dtls/fairdatapoint/entity/membership/Membership.java
index 0bf0a8cac37fb6b3f89e1e98a79d36d0d0c5550a..fb2fa00168ae6cc178c0b26713067db91458d154 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/membership/Membership.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/membership/Membership.java
@@ -46,10 +46,9 @@ public class Membership {
 
     protected List<MembershipPermission> permissions;
 
-    protected List<MembershipEntity> allowedEntities;
+    protected List<String> allowedEntities;
 
-    public Membership(String uuid, String name, List<MembershipPermission> permissions,
-                      List<MembershipEntity> allowedEntities) {
+    public Membership(String uuid, String name, List<MembershipPermission> permissions, List<String> allowedEntities) {
         this.uuid = uuid;
         this.name = name;
         this.permissions = permissions;
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/Metadata.java b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/Metadata.java
index 38b5af070f0481f027cc67972c8b0fa3af04a065..82bc1b0752b012f3df202c17e60739ff7dce4cd7 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/Metadata.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/Metadata.java
@@ -22,5 +22,23 @@
  */
 package nl.dtls.fairdatapoint.entity.metadata;
 
+import lombok.*;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Document
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
 public class Metadata {
+
+    @Id
+    protected ObjectId id;
+
+    protected String uri;
+
+    protected MetadataState state;
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataGetter.java b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataGetter.java
index 2962c76218cf2809b6d566143861cfb22e3cbec0..cbc28f421e869497efd7fcdb1aab027f23ab1979 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataGetter.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataGetter.java
@@ -23,7 +23,6 @@
 package nl.dtls.fairdatapoint.entity.metadata;
 
 import nl.dtls.fairdatapoint.util.ValueFactoryHelper;
-import nl.dtls.fairdatapoint.vocabulary.DATACITE;
 import nl.dtls.fairdatapoint.vocabulary.FDP;
 import nl.dtls.fairdatapoint.vocabulary.R3D;
 import org.eclipse.rdf4j.model.IRI;
@@ -32,8 +31,9 @@ import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.model.vocabulary.DCAT;
 import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
 import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
 
-import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -52,7 +52,7 @@ public class MetadataGetter {
     }
 
     public static Identifier getMetadataIdentifier(Model metadata) {
-        return getIdentifier(metadata, DATACITE.HASIDENTIFIER);
+        return getIdentifier(metadata, FDP.METADATAIDENTIFIER);
     }
 
     public static IRI getParent(Model metadata) {
@@ -77,6 +77,10 @@ public class MetadataGetter {
         return l(getObjectBy(metadata, null, DCTERMS.TITLE));
     }
 
+    public static Literal getLabel(Model metadata) {
+        return l(getObjectBy(metadata, null, RDFS.LABEL));
+    }
+
     public static Literal getDescription(Model metadata) {
         return l(getObjectBy(metadata, null, DCTERMS.DESCRIPTION));
     }
@@ -89,17 +93,22 @@ public class MetadataGetter {
         return i(getObjectBy(metadata, null, DCTERMS.LICENSE));
     }
 
-    public static LocalDateTime getIssued(Model metadata) {
+    public static OffsetDateTime getIssued(Model metadata) {
         String result = getStringObjectBy(metadata, null, FDP.METADATAISSUED);
         return result != null ? parseDateTimeLiteral(result) : null;
     }
 
-    public static LocalDateTime getModified(Model metadata) {
+    public static OffsetDateTime getModified(Model metadata) {
         String result = getStringObjectBy(metadata, null, FDP.METADATAMODIFIED);
         return result != null ? parseDateTimeLiteral(result) : null;
     }
 
-    public static LocalDateTime getMetadataModified(Model metadata) {
+    public static OffsetDateTime getMetadataIssued(Model metadata) {
+        String result = getStringObjectBy(metadata, null, DCTERMS.ISSUED);
+        return result != null ? parseDateTimeLiteral(result) : null;
+    }
+
+    public static OffsetDateTime getMetadataModified(Model metadata) {
         String result = getStringObjectBy(metadata, null, DCTERMS.MODIFIED);
         return result != null ? parseDateTimeLiteral(result) : null;
     }
@@ -132,8 +141,8 @@ public class MetadataGetter {
     // ------------------------------------------------------------------------------------------------------------
     //  Utils
     // ------------------------------------------------------------------------------------------------------------
-    private static LocalDateTime parseDateTimeLiteral(String literal) {
-        return LocalDateTime.parse(literal, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+    private static OffsetDateTime parseDateTimeLiteral(String literal) {
+        return OffsetDateTime.parse(literal, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
     }
 
     public static Identifier getIdentifier(Model metadata, IRI pred) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataSetter.java b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataSetter.java
index c97a4e09248afd20726af07995a043a75192ea15..29d8d0771e889782c09f7c985c954ac785b7d168 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataSetter.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataSetter.java
@@ -22,8 +22,8 @@
  */
 package nl.dtls.fairdatapoint.entity.metadata;
 
-import nl.dtls.fairdatapoint.vocabulary.DATACITE;
 import nl.dtls.fairdatapoint.vocabulary.FDP;
+import nl.dtls.fairdatapoint.vocabulary.R3D;
 import nl.dtls.fairdatapoint.vocabulary.Sio;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Literal;
@@ -53,7 +53,7 @@ public class MetadataSetter {
     }
 
     public static void setMetadataIdentifier(Model metadata, IRI uri, Identifier identifier) {
-        setIdentifier(metadata, uri, identifier, DATACITE.HASIDENTIFIER);
+        setIdentifier(metadata, uri, identifier, FDP.METADATAIDENTIFIER);
     }
 
     public static void setParent(Model metadata, IRI uri, IRI parent) {
@@ -69,7 +69,10 @@ public class MetadataSetter {
     // ------------------------------------------------------------------------------------------------------------
     public static void setTitle(Model metadata, IRI uri, Literal title) {
         update(metadata, uri, DCTERMS.TITLE, title);
-        update(metadata, uri, RDFS.LABEL, title);
+    }
+
+    public static void setLabel(Model metadata, IRI uri, Literal label) {
+        update(metadata, uri, RDFS.LABEL, label);
     }
 
     public static void setDescription(Model metadata, IRI uri, Literal description) {
@@ -114,6 +117,10 @@ public class MetadataSetter {
     //  Custom
     // ------------------------------------------------------------------------------------------------------------
 
+    public static void setRepositoryIdentifier(Model metadata, IRI uri, Identifier identifier) {
+        setIdentifier(metadata, uri, identifier, R3D.REPOSITORYIDENTIFIER);
+    }
+
     public static void setPublisher(Model metadata, IRI uri, Agent publisher) {
         setAgent(metadata, uri, publisher, DCTERMS.PUBLISHER);
     }
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataState.java b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataState.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4a0c96b172f5d777ed2ef3f69e9f32bdc87938a
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/metadata/MetadataState.java
@@ -0,0 +1,29 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.metadata;
+
+public enum MetadataState {
+
+    DRAFT, PUBLISHED
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinition.java b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinition.java
index f5fa887031230262e5e08ed02fda60c9863793bb..920d3f81f85855d7ec883ddcb960e926c4f38352 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinition.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinition.java
@@ -22,11 +22,13 @@
  */
 package nl.dtls.fairdatapoint.entity.resource;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.*;
 import org.bson.types.ObjectId;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @Document
@@ -34,41 +36,34 @@ import java.util.List;
 @AllArgsConstructor
 @Getter
 @Setter
+@EqualsAndHashCode
 @Builder(toBuilder = true)
 public class ResourceDefinition {
 
     @Id
+    @JsonIgnore
     protected ObjectId id;
 
     protected String uuid;
 
     protected String name;
 
-    protected String uriPrefix;
+    protected String urlPrefix;
 
-    protected String rdfType;
+    protected List<String> shapeUuids = new ArrayList<>();
 
-    protected String specs;
+    protected List<ResourceDefinitionChild> children = new ArrayList<>();
 
-    protected List<String> shaclTargetClasses;
+    protected List<ResourceDefinitionLink> externalLinks = new ArrayList<>();
 
-    protected String child;
-
-    protected String parentResourceDefinitionUuid;
-
-    protected String childResourceDefinitionUuid;
-
-    public ResourceDefinition(String uuid, String name, String uriPrefix, String rdfType, String specs,
-                              List<String> shaclTargetClasses, String child, String parentResourceDefinitionUuid,
-                              String childResourceDefinitionUuid) {
+    public ResourceDefinition(String uuid, String name, String urlPrefix, List<String> shapeUuids,
+                              List<ResourceDefinitionChild> children, List<ResourceDefinitionLink> externalLinks) {
         this.uuid = uuid;
         this.name = name;
-        this.uriPrefix = uriPrefix;
-        this.rdfType = rdfType;
-        this.specs = specs;
-        this.shaclTargetClasses = shaclTargetClasses;
-        this.child = child;
-        this.parentResourceDefinitionUuid = parentResourceDefinitionUuid;
-        this.childResourceDefinitionUuid = childResourceDefinitionUuid;
+        this.urlPrefix = urlPrefix;
+        this.shapeUuids = shapeUuids;
+        this.children = children;
+        this.externalLinks = externalLinks;
     }
+
 }
\ No newline at end of file
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChild.java b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChild.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f62ca92dc81381ed6c601ae9a399bbcf1b9b49f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChild.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.resource;
+
+import lombok.*;
+import nl.dtls.fairdatapoint.api.validator.ValidIri;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@Builder(toBuilder = true)
+public class ResourceDefinitionChild {
+
+    @NotBlank
+    protected String resourceDefinitionUuid;
+
+    @NotBlank
+    @ValidIri
+    protected String relationUri;
+
+    @Valid
+    protected ResourceDefinitionChildListView listView;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChildListView.java b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChildListView.java
new file mode 100644
index 0000000000000000000000000000000000000000..43474dbc28e5baf4934fa99aacdac11bbcd8a5c8
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChildListView.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.resource;
+
+import lombok.*;
+import nl.dtls.fairdatapoint.api.validator.ValidIri;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@Builder(toBuilder = true)
+public class ResourceDefinitionChildListView {
+
+    @NotBlank
+    protected String title;
+
+    @ValidIri
+    protected String tagsUri;
+
+    @Valid
+    protected List<ResourceDefinitionChildListViewMetadata> metadata;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChildListViewMetadata.java b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChildListViewMetadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fc935c3e1ca9893769886c9ff49c0d25fa14a10
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionChildListViewMetadata.java
@@ -0,0 +1,44 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.resource;
+
+import lombok.*;
+import nl.dtls.fairdatapoint.api.validator.ValidIri;
+
+import javax.validation.constraints.NotBlank;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class ResourceDefinitionChildListViewMetadata {
+
+    @NotBlank
+    protected String title;
+
+    @NotBlank
+    @ValidIri
+    protected String propertyUri;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionLink.java b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionLink.java
new file mode 100644
index 0000000000000000000000000000000000000000..b132308f9f9d6ac6c5fe6bab7ee03bdb95407415
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/resource/ResourceDefinitionLink.java
@@ -0,0 +1,44 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.resource;
+
+import lombok.*;
+import nl.dtls.fairdatapoint.api.validator.ValidIri;
+
+import javax.validation.constraints.NotBlank;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+public class ResourceDefinitionLink {
+
+    @NotBlank
+    protected String title;
+
+    @NotBlank
+    @ValidIri
+    protected String propertyUri;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/search/SearchResult.java b/src/main/java/nl/dtls/fairdatapoint/entity/search/SearchResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..54e51088d5adb5f13ccc959a3a1cf5218a6e88ed
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/search/SearchResult.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.search;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SearchResult {
+
+    private String uri;
+
+    private String type;
+
+    private String title;
+
+    private String description;
+
+    private SearchResultRelation relation;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/search/SearchResultRelation.java b/src/main/java/nl/dtls/fairdatapoint/entity/search/SearchResultRelation.java
new file mode 100644
index 0000000000000000000000000000000000000000..365909c8fd37763c51296d35a8e75868854c5887
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/search/SearchResultRelation.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.search;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode
+@Getter
+@Setter
+public class SearchResultRelation {
+
+    private String predicate;
+
+    private String object;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/settings/Settings.java b/src/main/java/nl/dtls/fairdatapoint/entity/settings/Settings.java
new file mode 100644
index 0000000000000000000000000000000000000000..698463cdc4a069adfb705f4064d8023b2e8d262f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/settings/Settings.java
@@ -0,0 +1,65 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.settings;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.*;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.List;
+import java.util.Map;
+
+@Document
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@Builder(toBuilder = true)
+public class Settings {
+
+    @Id
+    @JsonIgnore
+    protected ObjectId id;
+
+    private List<SettingsMetricsEntry> metadataMetrics;
+
+    private SettingsPing ping;
+
+    private static final List<SettingsMetricsEntry> DEFAULT_METRICS = List.of(
+            new SettingsMetricsEntry("https://purl.org/fair-metrics/FM_F1A", "https://www.ietf.org/rfc/rfc3986.txt"),
+            new SettingsMetricsEntry("https://purl.org/fair-metrics/FM_A1.1", "https://www.wikidata.org/wiki/Q8777")
+    );
+
+    public static Settings getDefault() {
+        return Settings.builder()
+                .metadataMetrics(DEFAULT_METRICS)
+                .ping(SettingsPing.builder()
+                        .enabled(true)
+                        .endpoints(List.of("https://home.fairdatapoint.org"))
+                        .build())
+                .build();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/settings/SettingsMetricsEntry.java b/src/main/java/nl/dtls/fairdatapoint/entity/settings/SettingsMetricsEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..552afaae6054c979983d72f1c0b52f200f5f2ff9
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/settings/SettingsMetricsEntry.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.settings;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@Builder(toBuilder = true)
+public class SettingsMetricsEntry {
+
+    private String metricUri;
+
+    private String resourceUri;
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/settings/SettingsPing.java b/src/main/java/nl/dtls/fairdatapoint/entity/settings/SettingsPing.java
new file mode 100644
index 0000000000000000000000000000000000000000..d13fca4455ded176b78c5d5caa65d53c6e24a04a
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/settings/SettingsPing.java
@@ -0,0 +1,42 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.entity.settings;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@EqualsAndHashCode
+@Builder(toBuilder = true)
+public class SettingsPing {
+
+    private boolean enabled;
+
+    @NotNull
+    private List<String> endpoints;
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/entity/shape/Shape.java b/src/main/java/nl/dtls/fairdatapoint/entity/shape/Shape.java
index 17492e947800f988cda948ab24669bc41b07facf..7a6188757db988513b761bf2fb6a9773fc977619 100755
--- a/src/main/java/nl/dtls/fairdatapoint/entity/shape/Shape.java
+++ b/src/main/java/nl/dtls/fairdatapoint/entity/shape/Shape.java
@@ -27,6 +27,8 @@ import org.bson.types.ObjectId;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 
+import java.util.Set;
+
 @Document
 @Getter
 @Setter
@@ -42,8 +44,11 @@ public class Shape {
 
     private String name;
 
+    private boolean published;
+
     private ShapeType type;
 
     private String definition;
 
+    private Set<String> targetClasses;
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/UtilityService.java b/src/main/java/nl/dtls/fairdatapoint/service/UtilityService.java
new file mode 100644
index 0000000000000000000000000000000000000000..30e1ea5a886629595765202d3b4ddbe2ea94fa0f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/UtilityService.java
@@ -0,0 +1,42 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service;
+
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
+import nl.dtls.fairdatapoint.util.HttpUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Service
+public class UtilityService {
+
+    @Autowired
+    private InstanceProperties instanceProperties;
+
+    public String getRemoteAddr(HttpServletRequest request) {
+        return HttpUtil.getClientIpAddress(request, instanceProperties.isBehindProxy());
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/actuator/AppInfoContributor.java b/src/main/java/nl/dtls/fairdatapoint/service/actuator/AppInfoContributor.java
index 0fa5b0a17203d700de0d187b80071c68ce55c244..d726951425eb4dba38e589ced9fd8af5debcb5d2 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/actuator/AppInfoContributor.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/actuator/AppInfoContributor.java
@@ -47,10 +47,10 @@ public class AppInfoContributor implements InfoContributor {
     @Override
     public void contribute(Info.Builder builder) {
         builder.withDetail("name", "FAIR Data Point");
-        if (tag != null && !tag.isEmpty()) {
-            builder.withDetail("version", tag);
-        } else {
+        if (tag == null || tag.isEmpty()) {
             builder.withDetail("version", format("%s~%s", branch, commitShort));
+        } else {
+            builder.withDetail("version", format("%s~%s", tag, commitShort));
         }
         builder.withDetail("builtAt", buildTime);
     }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/apikey/ApiKeyMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/apikey/ApiKeyMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d64b4e8f6922f5023015f9a2a4138481bfe3593
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/apikey/ApiKeyMapper.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.apikey;
+
+import nl.dtls.fairdatapoint.api.dto.apikey.ApiKeyDTO;
+import nl.dtls.fairdatapoint.entity.apikey.ApiKey;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ApiKeyMapper {
+
+    public ApiKeyDTO toDTO(ApiKey apiKey) {
+        return new ApiKeyDTO(apiKey.getUuid(), apiKey.getToken());
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/apikey/ApiKeyService.java b/src/main/java/nl/dtls/fairdatapoint/service/apikey/ApiKeyService.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb3f17ed1d1c8147ac37f79bd1f9b78d1ad3f96f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/apikey/ApiKeyService.java
@@ -0,0 +1,113 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.apikey;
+
+import nl.dtls.fairdatapoint.api.dto.apikey.ApiKeyDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.ApiKeyRepository;
+import nl.dtls.fairdatapoint.entity.apikey.ApiKey;
+import nl.dtls.fairdatapoint.entity.exception.ForbiddenException;
+import nl.dtls.fairdatapoint.entity.exception.UnauthorizedException;
+import nl.dtls.fairdatapoint.entity.user.User;
+import nl.dtls.fairdatapoint.entity.user.UserRole;
+import nl.dtls.fairdatapoint.service.security.MongoAuthenticationService;
+import nl.dtls.fairdatapoint.service.user.CurrentUserService;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Service
+public class ApiKeyService {
+
+    @Autowired
+    private ApiKeyRepository apiKeyRepository;
+
+    @Autowired
+    private CurrentUserService currentUserService;
+
+    @Autowired
+    private MongoAuthenticationService mongoAuthenticationService;
+
+    @Autowired
+    private ApiKeyMapper apiKeyMapper;
+
+    public List<ApiKeyDTO> getAll() {
+        Optional<User> oUser = currentUserService.getCurrentUser();
+        if (oUser.isEmpty()) {
+            throw new UnauthorizedException("You have to be log in");
+        }
+        User user = oUser.get();
+        List<ApiKey> apiKeys = apiKeyRepository.findByUserUuid(user.getUuid());
+        return apiKeys
+                .stream()
+                .map(apiKeyMapper::toDTO)
+                .collect(Collectors.toList());
+    }
+
+    public ApiKeyDTO create() {
+        Optional<User> oUser = currentUserService.getCurrentUser();
+        if (oUser.isEmpty()) {
+            throw new UnauthorizedException("You have to be log in");
+        }
+        User user = oUser.get();
+        String generatedString = RandomStringUtils.random(128, true, true);
+        String uuid = UUID.randomUUID().toString();
+        ApiKey apiKey = new ApiKey(null, uuid, user.getUuid(), generatedString);
+        apiKeyRepository.save(apiKey);
+        return apiKeyMapper.toDTO(apiKey);
+    }
+
+    public boolean delete(String uuid) {
+        Optional<ApiKey> oApiKey = apiKeyRepository.findByUuid(uuid);
+        if (oApiKey.isEmpty()) {
+            return false;
+        }
+        ApiKey apiKey = oApiKey.get();
+        Optional<User> oCurrentUser = currentUserService.getCurrentUser();
+        if (oCurrentUser.isEmpty()) {
+            throw new ForbiddenException("You have to be log in");
+        }
+        User currentUser = oCurrentUser.get();
+        if (currentUser.getRole().equals(UserRole.ADMIN) || apiKey.getUserUuid().equals(currentUser.getUuid())) {
+            apiKeyRepository.delete(apiKey);
+            return true;
+        } else {
+            throw new ForbiddenException("You are not allow to delete the entry");
+        }
+    }
+
+    public Authentication getAuthentication(String token) {
+        Optional<ApiKey> oApiKey = apiKeyRepository.findByToken(token);
+        if (oApiKey.isEmpty()) {
+            throw new UnauthorizedException("Invalid or non-existing API key");
+        }
+        ApiKey apiKey = oApiKey.get();
+        return mongoAuthenticationService.getAuthentication(apiKey.getUserUuid());
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/config/ConfigService.java b/src/main/java/nl/dtls/fairdatapoint/service/config/ConfigService.java
index f6275b51d6e8c155ce67c8a6af72ddbbe6e84684..e0b8bb6466aa65dfab3a47f4887bb7e2eb91c30e 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/config/ConfigService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/config/ConfigService.java
@@ -23,10 +23,16 @@
 package nl.dtls.fairdatapoint.service.config;
 
 import nl.dtls.fairdatapoint.api.dto.config.BootstrapConfigDTO;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionDTO;
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 @Service
 public class ConfigService {
 
@@ -34,8 +40,15 @@ public class ConfigService {
     @Qualifier("persistentUrl")
     private String persistentUrl;
 
+    @Autowired
+    private InstanceProperties instanceProperties;
+
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
+
     public BootstrapConfigDTO getBootstrapConfig() {
-        return new BootstrapConfigDTO(persistentUrl);
+        List<ResourceDefinitionDTO> resourceDefinitions = resourceDefinitionService.getAll();
+        return new BootstrapConfigDTO(persistentUrl, resourceDefinitions, instanceProperties.isIndex());
     }
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardMapper.java
deleted file mode 100755
index 3ca7c0ef73ba5cc2c943bea76e636c4488f8326f..0000000000000000000000000000000000000000
--- a/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardMapper.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 DTL
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package nl.dtls.fairdatapoint.service.dashboard;
-
-import nl.dtls.fairdatapoint.api.dto.dashboard.DashboardItemDTO;
-import nl.dtls.fairdatapoint.api.dto.membership.MembershipDTO;
-import org.eclipse.rdf4j.model.Model;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-import java.util.Optional;
-
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getTitle;
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
-
-@Service
-public class DashboardMapper {
-
-    public DashboardItemDTO toCatalogDTO(Model c, List<DashboardItemDTO> datasets, Optional<MembershipDTO> membership) {
-        return new DashboardItemDTO(
-                getUri(c).toString(),
-                getTitle(c).getLabel(),
-                datasets,
-                membership
-        );
-    }
-
-    public DashboardItemDTO toDatasetDTO(Model d, List<DashboardItemDTO> datasets, Optional<MembershipDTO> membership) {
-        return new DashboardItemDTO(
-                getUri(d).toString(),
-                getTitle(d).getLabel(),
-                datasets,
-                membership
-        );
-    }
-
-    public DashboardItemDTO toDistributionDTO(Model d, Optional<MembershipDTO> membership) {
-        return new DashboardItemDTO(
-                getUri(d).toString(),
-                getTitle(d).getLabel(),
-                List.of(),
-                membership
-        );
-    }
-}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardService.java b/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardService.java
index a3aa138052c2e4c21796341eca48b996d4e2492c..84d6762605b78e50bd99b182f5b4f1bb88da79a5 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/dashboard/DashboardService.java
@@ -24,87 +24,85 @@ package nl.dtls.fairdatapoint.service.dashboard;
 
 import nl.dtls.fairdatapoint.api.dto.dashboard.DashboardItemDTO;
 import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.membership.MembershipDTO;
 import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
 import nl.dtls.fairdatapoint.service.member.MemberService;
 import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
+import nl.dtls.fairdatapoint.service.metadata.state.MetadataStateService;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.*;
-import static nl.dtls.fairdatapoint.util.ThrowingFunction.suppress;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getTitle;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
+import static nl.dtls.fairdatapoint.util.RdfUtil.getObjectsBy;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
 @Service
 public class DashboardService {
 
     @Autowired
-    @Qualifier("catalogMetadataService")
-    private MetadataService catalogMetadataService;
+    @Qualifier("genericMetadataService")
+    private MetadataService metadataService;
 
     @Autowired
-    @Qualifier("genericMetadataService")
-    private MetadataService genericMetadataService;
+    private MetadataStateService metadataStateService;
 
     @Autowired
     private MemberService memberService;
 
     @Autowired
-    private DashboardMapper dashboardMapper;
-
-    public List<DashboardItemDTO> getDashboard(IRI repositoryUri) throws MetadataServiceException {
-        Model repository = genericMetadataService.retrieve(repositoryUri);
-        return getDashboardCatalogs(repository);
-    }
-
-    private List<DashboardItemDTO> getDashboardCatalogs(Model fdpMetadata) throws MetadataServiceException {
-        List<Model> catalogs = catalogMetadataService.retrieve(getCatalogs(fdpMetadata));
-        return catalogs.stream()
-                .map(suppress(this::getDashboardCatalog))
-                .filter(c -> c.getMembership().isPresent() || c.getChildren().size() > 0)
-                .collect(Collectors.toList());
-    }
+    private ResourceDefinitionService resourceDefinitionService;
 
-    private DashboardItemDTO getDashboardCatalog(Model catalog) throws MetadataServiceException {
-        String catalogId = getMetadataIdentifier(catalog).getIdentifier().getLabel();
-        Optional<MemberDTO> oCatalogMember = memberService.getMemberForCurrentUser(catalogId, Metadata.class);
-        List<DashboardItemDTO> datasetDtos = getDashboardDatasets(catalog);
-        return dashboardMapper.toCatalogDTO(catalog, datasetDtos, oCatalogMember.map(MemberDTO::getMembership));
-    }
-
-    private List<DashboardItemDTO> getDashboardDatasets(Model catalog) throws MetadataServiceException {
-        List<Model> datasets = genericMetadataService.retrieve(getDatasets(catalog));
-        return datasets.stream()
-                .map(suppress(this::getDashboardDataset))
-                .filter(d -> d.getMembership().isPresent() || d.getChildren().size() > 0)
-                .collect(Collectors.toList());
-    }
-
-    private DashboardItemDTO getDashboardDataset(Model dataset) throws MetadataServiceException {
-        String datasetId = getMetadataIdentifier(dataset).getIdentifier().getLabel();
-        Optional<MemberDTO> oDatasetMember = memberService.getMemberForCurrentUser(datasetId, Metadata.class);
-        List<DashboardItemDTO> distributionDtos = getDashboardDistributions(dataset);
-        return dashboardMapper.toDatasetDTO(dataset, distributionDtos, oDatasetMember.map(MemberDTO::getMembership));
-    }
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
 
-    private List<DashboardItemDTO> getDashboardDistributions(Model dataset) throws MetadataServiceException {
-        List<Model> distributions = genericMetadataService.retrieve(getDistributions(dataset));
-        return distributions.stream()
-                .map(this::getDashboardDistribution)
-                .filter(d -> d.getMembership().isPresent())
-                .collect(Collectors.toList());
+    public List<DashboardItemDTO> getDashboard(IRI repositoryUri) throws MetadataServiceException {
+        ResourceDefinition rd = resourceDefinitionService.getByUrlPrefix("");
+        Model repository = metadataService.retrieve(repositoryUri);
+        return getDashboardItem(repositoryUri, repository, rd).getChildren();
     }
 
-    private DashboardItemDTO getDashboardDistribution(Model distribution) {
-        String distributionId = getMetadataIdentifier(distribution).getIdentifier().getLabel();
-        Optional<MemberDTO> oDistributionMember = memberService.getMemberForCurrentUser(distributionId, Metadata.class);
-        return dashboardMapper.toDistributionDTO(distribution, oDistributionMember.map(MemberDTO::getMembership));
+    private DashboardItemDTO getDashboardItem(IRI metadataUri, Model model, ResourceDefinition rd) throws MetadataServiceException {
+        List<DashboardItemDTO> children = new ArrayList<>();
+        for (ResourceDefinitionChild rdChild : rd.getChildren()) {
+            IRI relationUri = i(rdChild.getRelationUri());
+            for (org.eclipse.rdf4j.model.Value childUri : getObjectsBy(model, metadataUri, relationUri)) {
+                IRI childIri = i(childUri.stringValue());
+                DashboardItemDTO child = getDashboardItem(
+                        childIri,
+                        metadataService.retrieve(childIri),
+                        resourceDefinitionCache.getByUuid(rdChild.getResourceDefinitionUuid())
+                );
+                children.add(child);
+            }
+        }
+
+        Optional<MemberDTO> oMember = memberService.getMemberForCurrentUser(metadataUri.stringValue(), Metadata.class);
+        Optional<MembershipDTO> membership = oMember.map(MemberDTO::getMembership);
+        Metadata state = metadataStateService.get(metadataUri);
+        return new DashboardItemDTO(
+                metadataUri.toString(),
+                getTitle(model).getLabel(),
+                children
+                        .stream()
+                        .filter(e -> e.getMembership().isPresent() || e.getChildren().size() > 0)
+                        .collect(Collectors.toList()),
+                membership,
+                state.getState()
+        );
     }
 
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/common/IndexFeatureAspect.java b/src/main/java/nl/dtls/fairdatapoint/service/index/common/IndexFeatureAspect.java
new file mode 100644
index 0000000000000000000000000000000000000000..87c37b3afa955efd38d9aa7e4e7ab16e52c6bd88
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/common/IndexFeatureAspect.java
@@ -0,0 +1,50 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.common;
+
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
+import nl.dtls.fairdatapoint.entity.exception.FeatureDisabledException;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class IndexFeatureAspect {
+
+    @Autowired
+    private InstanceProperties instanceProperties;
+
+    @Around("@annotation(nl.dtls.fairdatapoint.service.index.common.RequiredEnabledIndexFeature)")
+    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
+        if (!instanceProperties.isIndex()) {
+            throw new FeatureDisabledException("Index functionality is turn off");
+        }
+        return joinPoint.proceed();
+    }
+
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/common/RequiredEnabledIndexFeature.java b/src/main/java/nl/dtls/fairdatapoint/service/index/common/RequiredEnabledIndexFeature.java
new file mode 100644
index 0000000000000000000000000000000000000000..705a810e245532a9208d0bb2f670623091accba0
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/common/RequiredEnabledIndexFeature.java
@@ -0,0 +1,34 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequiredEnabledIndexFeature {
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/entry/IndexEntryMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/index/entry/IndexEntryMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8f1b7b16b52e297cbf12a7b037b6bf24585ed2b
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/entry/IndexEntryMapper.java
@@ -0,0 +1,81 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.entry;
+
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDetailDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryStateDTO;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntryState;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.service.index.event.EventMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+@Service
+public class IndexEntryMapper {
+
+    @Autowired
+    private EventMapper eventMapper;
+
+    public IndexEntryDTO toDTO(IndexEntry indexEntry, Instant validThreshold) {
+        return new IndexEntryDTO(
+                indexEntry.getUuid(),
+                indexEntry.getClientUrl(),
+                toStateDTO(indexEntry.getState(), indexEntry.getLastRetrievalTime(), validThreshold),
+                indexEntry.getRegistrationTime().toString(),
+                indexEntry.getModificationTime().toString()
+        );
+    }
+
+    public IndexEntryDetailDTO toDetailDTO(IndexEntry indexEntry, Iterable<Event> events, Instant validThreshold) {
+        return new IndexEntryDetailDTO(
+                indexEntry.getUuid(),
+                indexEntry.getClientUrl(),
+                toStateDTO(indexEntry.getState(), indexEntry.getLastRetrievalTime(), validThreshold),
+                indexEntry.getCurrentMetadata(),
+                StreamSupport.stream(events.spliterator(), false)
+                        .map(eventMapper::toDTO)
+                        .collect(Collectors.toList()),
+                indexEntry.getRegistrationTime().toString(),
+                indexEntry.getModificationTime().toString(),
+                indexEntry.getLastRetrievalTime().toString()
+        );
+    }
+
+    public IndexEntryStateDTO toStateDTO(IndexEntryState state, Instant lastRetrievalTime, Instant validThreshold) {
+        return switch (state) {
+            case Unknown -> IndexEntryStateDTO.UNKNOWN;
+            case Valid -> lastRetrievalTime.isAfter(validThreshold)
+                    ? IndexEntryStateDTO.ACTIVE
+                    : IndexEntryStateDTO.INACTIVE;
+            case Invalid -> IndexEntryStateDTO.INVALID;
+            case Unreachable -> IndexEntryStateDTO.UNREACHABLE;
+        };
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/entry/IndexEntryService.java b/src/main/java/nl/dtls/fairdatapoint/service/index/entry/IndexEntryService.java
new file mode 100644
index 0000000000000000000000000000000000000000..151030295f27c8f094b509c3268350514f051a12
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/entry/IndexEntryService.java
@@ -0,0 +1,168 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.entry;
+
+import lombok.extern.log4j.Log4j2;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDetailDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryInfoDTO;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryStateDTO;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntryState;
+import nl.dtls.fairdatapoint.service.index.common.RequiredEnabledIndexFeature;
+import nl.dtls.fairdatapoint.service.index.event.EventService;
+import nl.dtls.fairdatapoint.service.index.settings.IndexSettingsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.Valid;
+import java.time.Instant;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryStateDTO.*;
+
+@Service
+@Validated
+@Log4j2
+public class IndexEntryService {
+
+    @Autowired
+    private IndexEntryRepository repository;
+
+    @Autowired
+    private IndexSettingsService indexSettingsService;
+
+    @Autowired
+    private EventService eventService;
+
+    @Autowired
+    private IndexEntryMapper mapper;
+
+    @RequiredEnabledIndexFeature
+    public Iterable<IndexEntry> getAllEntries() {
+        return repository.findAll();
+    }
+
+    @RequiredEnabledIndexFeature
+    public List<IndexEntryDTO> getAllEntriesAsDTOs() {
+        Instant validThreshold = getValidThreshold();
+        return StreamSupport.stream(getAllEntries().spliterator(), true).map(it -> mapper.toDTO(it, validThreshold)).collect(Collectors.toList());
+    }
+
+    @RequiredEnabledIndexFeature
+    public Page<IndexEntry> getEntriesPage(Pageable pageable, String state) {
+        Instant validThreshold = getValidThreshold();
+        if (state.equalsIgnoreCase(ACTIVE.name())) {
+            return repository.findAllByStateEqualsAndLastRetrievalTimeAfter(pageable, IndexEntryState.Valid,
+                    validThreshold);
+        }
+        if (state.equalsIgnoreCase(IndexEntryStateDTO.INACTIVE.name())) {
+            return repository.findAllByStateEqualsAndLastRetrievalTimeBefore(pageable, IndexEntryState.Valid,
+                    validThreshold);
+        }
+        if (state.equalsIgnoreCase(IndexEntryStateDTO.UNREACHABLE.name())) {
+            return repository.findAllByStateEquals(pageable, IndexEntryState.Unreachable);
+        }
+        if (state.equalsIgnoreCase(IndexEntryStateDTO.INVALID.name())) {
+            return repository.findAllByStateEquals(pageable, IndexEntryState.Invalid);
+        }
+        if (state.equalsIgnoreCase(IndexEntryStateDTO.UNKNOWN.name())) {
+            return repository.findAllByStateEquals(pageable, IndexEntryState.Unknown);
+        }
+        return repository.findAll(pageable);
+    }
+
+    @RequiredEnabledIndexFeature
+    public Page<IndexEntryDTO> getEntriesPageDTOs(Pageable pageable, String state) {
+        Instant validThreshold = getValidThreshold();
+        return getEntriesPage(pageable, state).map(it -> mapper.toDTO(it, validThreshold));
+    }
+
+    @RequiredEnabledIndexFeature
+    public Optional<IndexEntry> getEntry(String uuid) {
+        return repository.findByUuid(uuid);
+    }
+
+    @RequiredEnabledIndexFeature
+    public Optional<IndexEntryDetailDTO> getEntryDetailDTO(String uuid) {
+        Instant validThreshold = getValidThreshold();
+        return getEntry(uuid).map(entry -> mapper.toDetailDTO(entry, eventService.getEvents(entry.getUuid()), validThreshold));
+    }
+
+    @RequiredEnabledIndexFeature
+    public IndexEntryInfoDTO getEntriesInfo() {
+        Instant validThreshold = getValidThreshold();
+        Map<String, Long> entriesCount = new HashMap<>();
+        entriesCount.put("ALL", repository.count());
+        entriesCount.put(UNKNOWN.name(), repository.countAllByStateEquals(IndexEntryState.Unknown));
+        entriesCount.put(ACTIVE.name(),
+                repository.countAllByStateEqualsAndLastRetrievalTimeAfter(IndexEntryState.Valid, validThreshold));
+        entriesCount.put(INACTIVE.name(),
+                repository.countAllByStateEqualsAndLastRetrievalTimeBefore(IndexEntryState.Valid, validThreshold));
+        entriesCount.put(UNREACHABLE.name(), repository.countAllByStateEquals(IndexEntryState.Unreachable));
+        entriesCount.put(INVALID.name(), repository.countAllByStateEquals(IndexEntryState.Invalid));
+        return new IndexEntryInfoDTO(entriesCount);
+    }
+
+    @RequiredEnabledIndexFeature
+    public IndexEntry storeEntry(@Valid PingDTO pingDTO) {
+        var clientUrl = pingDTO.getClientUrl();
+        var entity = repository.findByClientUrl(clientUrl);
+        var now = Instant.now();
+
+        final IndexEntry entry;
+        if (entity.isPresent()) {
+            log.info("Updating timestamp of existing entry {}", clientUrl);
+            entry = entity.orElseThrow();
+        } else {
+            log.info("Storing new entry {}", clientUrl);
+            entry = new IndexEntry();
+            entry.setUuid(UUID.randomUUID().toString());
+            entry.setClientUrl(clientUrl);
+            entry.setRegistrationTime(now);
+        }
+
+        entry.setModificationTime(now);
+        return repository.save(entry);
+    }
+
+    @RequiredEnabledIndexFeature
+    @PreAuthorize("hasRole('ADMIN')")
+    public void deleteEntry(String uuid) {
+        IndexEntry entry = repository.findByUuid(uuid).orElseThrow(() -> new ResourceNotFoundException("Index entry not found"));
+        repository.delete(entry);
+    }
+
+    private Instant getValidThreshold() {
+        return Instant.now().minus(indexSettingsService.getOrDefaults().getPing().getValidDuration());
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/event/EventMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/index/event/EventMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9b03b7be29119de95618e92a4e6d306564c30cf
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/event/EventMapper.java
@@ -0,0 +1,56 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.event;
+
+
+import nl.dtls.fairdatapoint.api.dto.index.event.EventDTO;
+import nl.dtls.fairdatapoint.entity.index.event.AdminTrigger;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Service
+public class EventMapper {
+
+    private static final Integer VERSION = 1;
+
+    public EventDTO toDTO(Event event) {
+        return new EventDTO(
+                event.getUuid(),
+                event.getType(),
+                event.getCreated().toString(),
+                event.getFinished().toString()
+        );
+    }
+
+    public Event toAdminTriggerEvent(HttpServletRequest request, Authentication authentication, String clientUrl, String remoteAddr) {
+        var adminTrigger = new AdminTrigger();
+        adminTrigger.setRemoteAddr(remoteAddr);
+        adminTrigger.setTokenName(authentication.getName());
+        adminTrigger.setClientUrl(clientUrl);
+        return new Event(VERSION, adminTrigger);
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/event/EventService.java b/src/main/java/nl/dtls/fairdatapoint/service/index/event/EventService.java
new file mode 100644
index 0000000000000000000000000000000000000000..327d767f96844d520eaa6c36b7b6d0f40918c385
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/event/EventService.java
@@ -0,0 +1,261 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.event;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.SneakyThrows;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.EventRepository;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntryState;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.EventType;
+import nl.dtls.fairdatapoint.entity.index.exception.IncorrectPingFormatException;
+import nl.dtls.fairdatapoint.entity.index.exception.PingDeniedException;
+import nl.dtls.fairdatapoint.entity.index.exception.RateLimitException;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+import nl.dtls.fairdatapoint.entity.index.http.ExchangeState;
+import nl.dtls.fairdatapoint.service.UtilityService;
+import nl.dtls.fairdatapoint.service.index.common.RequiredEnabledIndexFeature;
+import nl.dtls.fairdatapoint.service.index.entry.IndexEntryService;
+import nl.dtls.fairdatapoint.service.index.settings.IndexSettingsService;
+import nl.dtls.fairdatapoint.service.index.webhook.WebhookService;
+import nl.dtls.fairdatapoint.util.HttpUtil;
+import org.eclipse.rdf4j.util.iterators.EmptyIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import java.time.Instant;
+import java.util.Optional;
+
+@Service
+public class EventService {
+    private static final Logger logger = LoggerFactory.getLogger(EventService.class);
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @Autowired
+    private ThreadPoolTaskExecutor executor;
+
+    @Autowired
+    private EventRepository eventRepository;
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    @Autowired
+    @Lazy
+    private IndexEntryService indexEntryService;
+
+    @Autowired
+    private WebhookService webhookService;
+
+    @Autowired
+    private EventMapper eventMapper;
+
+    @Autowired
+    private UtilityService utilityService;
+
+    @Autowired
+    private IncomingPingUtils incomingPingUtils;
+
+    @Autowired
+    private IndexSettingsService indexSettingsService;
+
+    public Iterable<Event> getEvents(IndexEntry indexEntry) {
+        // TODO: make events pagination in the future
+        return eventRepository.getAllByRelatedTo(indexEntry, PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC,
+                "created")));
+    }
+
+    @RequiredEnabledIndexFeature
+    public Iterable<Event> getEvents(String indexEntryUuid) {
+        return indexEntryService.getEntry(indexEntryUuid).map(this::getEvents).orElse(EmptyIterator::new);
+    }
+
+    @RequiredEnabledIndexFeature
+    @SneakyThrows
+    public Event acceptIncomingPing(PingDTO reqDto, HttpServletRequest request) {
+        var remoteAddr = utilityService.getRemoteAddr(request);
+        var pingSettings = indexSettingsService.getOrDefaults().getPing();
+
+        if (indexSettingsService.isPingDenied(reqDto)) {
+            logger.info("Received ping is denied");
+            throw new PingDeniedException(reqDto.getClientUrl());
+        }
+
+        var rateLimitSince = Instant.now().minus(pingSettings.getRateLimitDuration());
+        var previousPings = eventRepository.findAllByIncomingPingExchangeRemoteAddrAndCreatedAfter(remoteAddr,
+                rateLimitSince);
+        if (previousPings.size() > pingSettings.getRateLimitHits()) {
+            logger.warn("Rate limit for PING reached by {}", remoteAddr);
+            throw new RateLimitException(String.format(
+                    "Rate limit reached for %s (max. %d per %s) - PING ignored",
+                    remoteAddr, pingSettings.getRateLimitHits(), pingSettings.getRateLimitDuration().toString())
+            );
+        }
+
+        var event = incomingPingUtils.prepareEvent(reqDto, request, remoteAddr);
+        eventRepository.save(event);
+        event.execute();
+        try {
+            var indexEntry = indexEntryService.storeEntry(reqDto);
+            event.getIncomingPing().setNewEntry(indexEntry.getRegistrationTime().equals(indexEntry.getModificationTime()));
+            event.getIncomingPing().getExchange().getResponse().setCode(204);
+            event.setRelatedTo(indexEntry);
+            logger.info("Accepted incoming ping as a new event");
+        } catch (Exception e) {
+            var ex = new IncorrectPingFormatException("Could not parse PING: " + e.getMessage());
+            event.getIncomingPing().getExchange().getResponse().setCode(400);
+            event.getIncomingPing().getExchange().getResponse().setBody(objectMapper.writeValueAsString(ex.getErrorDTO()));
+            event.setFinished(Instant.now());
+            eventRepository.save(event);
+            logger.info("Incoming ping has incorrect format: {}", e.getMessage());
+            throw ex;
+        }
+        event.setFinished(Instant.now());
+        return eventRepository.save(event);
+    }
+
+    private void processMetadataRetrieval(Event event) {
+        var retrievalSettings = indexSettingsService.getOrDefaults().getRetrieval();
+        var clientUrl = event.getRelatedTo().getClientUrl();
+        if (MetadataRetrievalUtils.shouldRetrieve(event, retrievalSettings.getRateLimitWait())) {
+            indexEntryRepository.save(event.getRelatedTo());
+            eventRepository.save(event);
+            event.execute();
+
+            logger.info("Retrieving metadata for {}", clientUrl);
+            MetadataRetrievalUtils.retrieveRepositoryMetadata(event, retrievalSettings.getTimeout());
+            Exchange ex = event.getMetadataRetrieval().getExchange();
+            if (ex.getState() == ExchangeState.Retrieved) {
+                try {
+                    logger.info("Parsing metadata for {}", clientUrl);
+                    var metadata = MetadataRetrievalUtils.parseRepositoryMetadata(ex.getResponse().getBody());
+                    if (metadata.isPresent()) {
+                        event.getMetadataRetrieval().setMetadata(metadata.get());
+                        event.getRelatedTo().setCurrentMetadata(metadata.get());
+                        event.getRelatedTo().setState(IndexEntryState.Valid);
+                        logger.info("Storing metadata for {}", clientUrl);
+                        indexEntryRepository.save(event.getRelatedTo());
+                    } else {
+                        logger.info("Repository not found in metadata for {}", clientUrl);
+                        event.getRelatedTo().setState(IndexEntryState.Invalid);
+                        event.getMetadataRetrieval().setError("Repository not found in metadata");
+                    }
+                } catch (Exception e) {
+                    logger.info("Cannot parse metadata for {}", clientUrl);
+                    event.getRelatedTo().setState(IndexEntryState.Invalid);
+                    event.getMetadataRetrieval().setError("Cannot parse metadata");
+                }
+            } else {
+                event.getRelatedTo().setState(IndexEntryState.Unreachable);
+                logger.info("Cannot retrieve metadata for {}: {}", clientUrl, ex.getError());
+            }
+        } else {
+            logger.info("Rate limit reached for {} (skipping metadata retrieval)", clientUrl);
+            event.getMetadataRetrieval().setError("Rate limit reached (skipping)");
+        }
+        event.getRelatedTo().setLastRetrievalTime(Instant.now());
+        event.finish();
+        event = eventRepository.save(event);
+        indexEntryRepository.save(event.getRelatedTo());
+        webhookService.triggerWebhooks(event);
+    }
+
+    @Async
+    @RequiredEnabledIndexFeature
+    public void triggerMetadataRetrieval(Event triggerEvent) {
+        logger.info("Initiating metadata retrieval triggered by {}", triggerEvent.getUuid());
+        Iterable<Event> events = MetadataRetrievalUtils.prepareEvents(triggerEvent, indexEntryService);
+        for (Event event : events) {
+            logger.info("Triggering metadata retrieval for {} as {}", event.getRelatedTo().getClientUrl(),
+                    event.getUuid());
+            try {
+                processMetadataRetrieval(event);
+            } catch (Exception e) {
+                logger.error("Failed to retrieve metadata: {}", e.getMessage());
+            }
+        }
+        logger.info("Finished metadata retrieval triggered by {}", triggerEvent.getUuid());
+    }
+
+    private void resumeUnfinishedEvents() {
+        logger.info("Resuming unfinished events");
+        for (Event event : eventRepository.getAllByFinishedIsNull()) {
+            logger.info("Resuming event {}", event.getUuid());
+
+            try {
+                if (event.getType() == EventType.MetadataRetrieval) {
+                    processMetadataRetrieval(event);
+                } else if (event.getType() == EventType.WebhookTrigger) {
+                    webhookService.processWebhookTrigger(event);
+                } else {
+                    logger.warn("Unknown event type {} ({})", event.getUuid(), event.getType());
+                }
+            } catch (Exception e) {
+                logger.error("Failed to resume event {}: {}", event.getUuid(), e.getMessage());
+            }
+        }
+        logger.info("Finished unfinished events");
+    }
+
+    @PostConstruct
+    public void startResumeUnfinishedEvents() {
+        executor.submit(this::resumeUnfinishedEvents);
+    }
+
+    @RequiredEnabledIndexFeature
+    public Event acceptAdminTrigger(HttpServletRequest request, PingDTO pingDTO) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Event event = eventMapper.toAdminTriggerEvent(request, authentication, pingDTO.getClientUrl(), utilityService.getRemoteAddr(request));
+        IndexEntry entry = indexEntryService.storeEntry(pingDTO);
+        event.setRelatedTo(entry);
+        event.finish();
+        return eventRepository.save(event);
+    }
+
+    @RequiredEnabledIndexFeature
+    public Event acceptAdminTriggerAll(HttpServletRequest request) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Event event = eventMapper.toAdminTriggerEvent(request, authentication, null, utilityService.getRemoteAddr(request));
+        event.finish();
+        return eventRepository.save(event);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/event/IncomingPingUtils.java b/src/main/java/nl/dtls/fairdatapoint/service/index/event/IncomingPingUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..762c39f41ca74cd0ff72437bbcd7847f12962825
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/event/IncomingPingUtils.java
@@ -0,0 +1,77 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.event;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.IncomingPing;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+import nl.dtls.fairdatapoint.entity.index.http.ExchangeDirection;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+@Service
+public class IncomingPingUtils {
+
+    private static final Integer VERSION = 1;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    public Event prepareEvent(PingDTO reqDto, HttpServletRequest request, String remoteAddr) {
+        var incomingPing = new IncomingPing();
+        var ex = new Exchange(ExchangeDirection.INCOMING, remoteAddr);
+        incomingPing.setExchange(ex);
+
+        ex.getRequest().setHeaders(getHeaders(request));
+        ex.getRequest().setFromHttpServletRequest(request);
+        try {
+            ex.getRequest().setBody(objectMapper.writeValueAsString(reqDto));
+        } catch (JsonProcessingException e) {
+            ex.getRequest().setBody(null);
+        }
+
+        return new Event(VERSION, incomingPing);
+    }
+
+    private Map<String, List<String>> getHeaders(HttpServletRequest request) {
+        Map<String, List<String>> map = new HashMap<>();
+        Iterator<String> requestI = request.getHeaderNames().asIterator();
+        while (requestI.hasNext()) {
+            String headerName = requestI.next();
+            List<String> headerValues = new ArrayList<>();
+            Iterator<String> headerI = request.getHeaders(headerName).asIterator();
+            while (headerI.hasNext()) {
+                headerValues.add(headerI.next());
+            }
+            map.put(headerName, headerValues);
+        }
+        return map;
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/event/MetadataRetrievalUtils.java b/src/main/java/nl/dtls/fairdatapoint/service/index/event/MetadataRetrievalUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4ee686a8a110e3e741eddced7a5be6410910a83
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/event/MetadataRetrievalUtils.java
@@ -0,0 +1,190 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.event;
+
+import nl.dtls.fairdatapoint.entity.index.entry.RepositoryMetadata;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.EventType;
+import nl.dtls.fairdatapoint.entity.index.event.MetadataRetrieval;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+import nl.dtls.fairdatapoint.entity.index.http.ExchangeDirection;
+import nl.dtls.fairdatapoint.entity.index.http.ExchangeState;
+import nl.dtls.fairdatapoint.service.index.entry.IndexEntryService;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.Statement;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.FOAF;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.rio.RDFFormat;
+import org.eclipse.rdf4j.rio.RDFParser;
+import org.eclipse.rdf4j.rio.Rio;
+import org.eclipse.rdf4j.rio.helpers.StatementCollector;
+import org.springframework.http.HttpHeaders;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
+
+public class MetadataRetrievalUtils {
+
+    private static final EventType EVENT_TYPE = EventType.MetadataRetrieval;
+
+    private static final Integer VERSION = 1;
+
+    private static final IRI REPOSITORY = SimpleValueFactory.getInstance().createIRI("http://www.re3data" +
+            ".org/schema/3-0#Repository");
+
+    private static final IRI COUNTRY = SimpleValueFactory.getInstance().createIRI("http://www.re3data" +
+            ".org/schema/3-0#institutionCountry");
+
+    private static final Map<IRI, String> MAPPING = Map.of(
+            DCTERMS.TITLE, "title",
+            DCTERMS.DESCRIPTION, "description",
+            DCTERMS.HAS_VERSION, "version",
+            DCTERMS.PUBLISHER, "publisher",
+            COUNTRY, "country"
+    );
+
+    private static final HttpClient client = HttpClient.newBuilder()
+            .version(HttpClient.Version.HTTP_2)
+            .followRedirects(HttpClient.Redirect.ALWAYS)
+            .build();
+
+    public static boolean shouldRetrieve(Event triggerEvent, Duration rateLimitWait) {
+        if (triggerEvent.getRelatedTo() == null) {
+            return false;
+        }
+        Instant lastRetrieval = triggerEvent.getRelatedTo().getLastRetrievalTime();
+        if (lastRetrieval == null) {
+            return true;
+        }
+        return Duration.between(lastRetrieval, Instant.now()).compareTo(rateLimitWait) > 0;
+    }
+
+    public static Iterable<Event> prepareEvents(Event triggerEvent, IndexEntryService indexEntryService) {
+        ArrayList<Event> events = new ArrayList<>();
+        if (triggerEvent.getType() == EventType.IncomingPing) {
+            events.add(new Event(VERSION, triggerEvent, triggerEvent.getRelatedTo(), new MetadataRetrieval()));
+        } else if (triggerEvent.getType() == EventType.AdminTrigger) {
+            if (triggerEvent.getAdminTrigger().getClientUrl() == null) {
+                indexEntryService.getAllEntries().forEach(
+                        entry -> events.add(new Event(VERSION, triggerEvent, entry, new MetadataRetrieval()))
+                );
+            } else {
+                events.add(new Event(VERSION, triggerEvent, triggerEvent.getRelatedTo(), new MetadataRetrieval()));
+            }
+        }
+        return events;
+    }
+
+    public static void retrieveRepositoryMetadata(Event event, Duration timeout) {
+        if (event.getType() != EVENT_TYPE) {
+            throw new IllegalArgumentException("Invalid event type");
+        }
+        var ex = new Exchange(ExchangeDirection.OUTGOING);
+        event.getMetadataRetrieval().setExchange(ex);
+        try {
+            HttpRequest request = HttpRequest.newBuilder()
+                    .uri(URI.create(event.getRelatedTo().getClientUrl()))
+                    .timeout(timeout)
+                    .header(HttpHeaders.ACCEPT, RDFFormat.TURTLE.getDefaultMIMEType())
+                    .GET().build();
+            ex.getRequest().setFromHttpRequest(request);
+            ex.setState(ExchangeState.Requested);
+            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+            ex.getResponse().setFromHttpResponse(response);
+            ex.setState(ExchangeState.Retrieved);
+        } catch (InterruptedException e) {
+            ex.setState(ExchangeState.Timeout);
+            ex.setError("Timeout");
+        } catch (IllegalArgumentException e) {
+            ex.setState(ExchangeState.Failed);
+            ex.setError("Invalid URI: " + e.getMessage());
+        } catch (IOException e) {
+            ex.setState(ExchangeState.Failed);
+            ex.setError("IO error: " + e.getMessage());
+        }
+    }
+
+    public static Optional<RepositoryMetadata> parseRepositoryMetadata(String metadata) throws IOException {
+        RDFParser parser = Rio.createParser(RDFFormat.TURTLE);
+        StatementCollector collector = new StatementCollector();
+        parser.setRDFHandler(collector);
+
+        parser.parse(new StringReader(metadata), String.valueOf(StandardCharsets.UTF_8));
+        ArrayList<Statement> statements = new ArrayList<>(collector.getStatements());
+
+        return findRepository(statements).map(repository -> extractRepositoryMetadata(statements, repository));
+    }
+
+    private static RepositoryMetadata extractRepositoryMetadata(ArrayList<Statement> statements, Resource repository) {
+        var repositoryMetadata = new RepositoryMetadata();
+        repositoryMetadata.setMetadataVersion(VERSION);
+        repositoryMetadata.setRepositoryUri(repository.toString());
+
+        Value publisher = null;
+        for (Statement st : statements) {
+            if (st.getSubject().equals(repository)) {
+                if (MAPPING.containsKey(st.getPredicate())) {
+                    repositoryMetadata.getMetadata().put(MAPPING.get(st.getPredicate()), st.getObject().stringValue());
+                }
+                if (st.getPredicate().equals(DCTERMS.PUBLISHER)) {
+                    publisher = st.getObject();
+                }
+            }
+        }
+
+        if (publisher != null) {
+            for (Statement st : statements) {
+                if (st.getSubject().equals(publisher)) {
+                    if (st.getPredicate().equals(FOAF.NAME)) {
+                        repositoryMetadata.getMetadata().put("publisherName", st.getObject().stringValue());
+                    }
+                }
+            }
+        }
+
+        return repositoryMetadata;
+    }
+
+    private static Optional<Resource> findRepository(ArrayList<Statement> statements) {
+        for (Statement st : statements) {
+            if (st.getPredicate().equals(RDF.TYPE) && st.getObject().equals(REPOSITORY)) {
+                return Optional.of(st.getSubject());
+            }
+        }
+        return Optional.empty();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/harvester/HarvesterService.java b/src/main/java/nl/dtls/fairdatapoint/service/index/harvester/HarvesterService.java
new file mode 100644
index 0000000000000000000000000000000000000000..19e08e8a47385092b9cf37b19a63897c17ec1257
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/harvester/HarvesterService.java
@@ -0,0 +1,157 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.harvester;
+
+import lombok.extern.log4j.Log4j2;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepository;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.vocabulary.LDP;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.rio.RDFFormat;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getChildren;
+import static nl.dtls.fairdatapoint.util.HttpUtil.getRdfContentType;
+import static nl.dtls.fairdatapoint.util.RdfIOUtil.read;
+import static nl.dtls.fairdatapoint.util.RdfIOUtil.readFile;
+import static nl.dtls.fairdatapoint.util.RdfUtil.getObjectsBy;
+import static nl.dtls.fairdatapoint.util.RdfUtil.getSubjectsBy;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+@Service
+@Log4j2
+public class HarvesterService {
+
+    private static final String DEFAULT_NAVIGATION_SHACL = "defaultNavigationShacl.ttl";
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private GenericMetadataRepository genericMetadataRepository;
+
+    @Async
+    public void harvest(String clientUrl) throws MetadataRepositoryException {
+        log.info(format("Start harvesting '%s'", clientUrl));
+
+        // 1. Get navigation relationships
+        List<IRI> navigationRelationships = getNavigationRelationships(clientUrl);
+
+        // 2. Harvest data
+        Map<String, Model> result = visitNode(clientUrl, navigationRelationships, new HashMap<>());
+
+        // 3. Store data
+        for (Map.Entry<String, Model> item : result.entrySet()) {
+            genericMetadataRepository.save(new ArrayList<>(item.getValue()), i(item.getKey()));
+        }
+
+        log.info(format("Harvesting for '%s' completed", clientUrl));
+    }
+
+    private List<IRI> getNavigationRelationships(String uri) {
+        Model model = readFile(DEFAULT_NAVIGATION_SHACL, "http://fairdatapoint.org");
+        return getObjectsBy(model, null, "http://www.w3.org/ns/shacl#path")
+                .stream()
+                .map(i -> i(i.stringValue()))
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    private Map<String, Model> visitNode(String uri, List<IRI> relationships, Map<String, Model> nodes) {
+        try {
+            Model model = makeRequest(uri);
+            nodes.put(uri, model);
+
+            List<Resource> containers = getSubjectsBy(model, RDF.TYPE, LDP.DIRECT_CONTAINER);
+            if (containers.size() > 0) {
+                // Get children through LDP links
+                for (Value container : containers) {
+                    for (Value child : getObjectsBy(model, i(container.stringValue()), LDP.CONTAINS)) {
+                        if (!nodes.containsKey(child.stringValue())) {
+                            nodes = visitNode(child.stringValue(), relationships, nodes);
+                        }
+                    }
+                }
+            } else {
+                // Get children through default navigation SHACL
+                for (IRI relationship : relationships) {
+                    List<IRI> children = getChildren(model, relationship);
+                    for (IRI child : children) {
+                        if (!nodes.containsKey(child.stringValue())) {
+                            nodes = visitNode(child.stringValue(), relationships, nodes);
+                        }
+                    }
+                }
+            }
+
+            return nodes;
+        } catch (HttpClientErrorException ex) {
+            return nodes;
+        }
+    }
+
+    private Model makeRequest(String uri) {
+        log.info(format("Making request to '%s'", uri));
+        HttpHeaders headers = new HttpHeaders();
+        headers.setAccept(List.of(MediaType.parseMediaType(RDFFormat.TURTLE.getDefaultMIMEType())));
+        HttpEntity<Void> entity = new HttpEntity<>(null, headers);
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                log.info(format("Request to '%s' failed", uri));
+                throw new HttpClientErrorException(response.getStatusCode());
+            }
+            RDFFormat rdfContentType = getRdfContentType(response.getHeaders().getContentType().getType());
+            log.info(format("Request to '%s' successfully received", uri));
+            Model result = read(response.getBody(), uri, rdfContentType);
+            log.info(format("Request to '%s' successfully parsed", uri));
+            return result;
+        } catch (RestClientException e) {
+            log.info(format("Request to '%s' failed", uri));
+            throw new HttpClientErrorException(
+                    HttpStatus.BAD_GATEWAY,
+                    ofNullable(e.getMessage()).orElse("HTTP request failed to proceed")
+            );
+        }
+    }
+
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/settings/IndexSettingsMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/index/settings/IndexSettingsMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2a3ed2263cf6313c11f187fcb77af8efa743d82
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/settings/IndexSettingsMapper.java
@@ -0,0 +1,98 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.settings;
+
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsPingDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsRetrievalDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsUpdateDTO;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettings;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsPing;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsRetrieval;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+
+@Service
+public class IndexSettingsMapper {
+
+    private IndexSettingsPingDTO toPingDTO(IndexSettingsPing indexSettingsPing) {
+        return new IndexSettingsPingDTO(
+                indexSettingsPing.getValidDuration().toString(),
+                indexSettingsPing.getRateLimitDuration().toString(),
+                indexSettingsPing.getRateLimitHits(),
+                indexSettingsPing.getDenyList()
+        );
+    }
+
+    private IndexSettingsRetrievalDTO toRetrievalDTO(IndexSettingsRetrieval indexSettingsRetrieval) {
+        return new IndexSettingsRetrievalDTO(
+                indexSettingsRetrieval.getRateLimitWait().toString(),
+                indexSettingsRetrieval.getTimeout().toString()
+        );
+    }
+
+    public IndexSettingsDTO toDTO(IndexSettings indexSettings) {
+        return new IndexSettingsDTO(
+                toRetrievalDTO(indexSettings.getRetrieval()),
+                toPingDTO(indexSettings.getPing()),
+                indexSettings.equals(IndexSettings.getDefault())
+        );
+    }
+
+    public IndexSettingsUpdateDTO toUpdateDTO(IndexSettings indexSettings) {
+        return new IndexSettingsUpdateDTO(
+                toRetrievalDTO(indexSettings.getRetrieval()),
+                toPingDTO(indexSettings.getPing())
+        );
+    }
+
+    private IndexSettingsPing fromDTO(IndexSettingsPingDTO dto, IndexSettingsPing ping) {
+        return
+                ping
+                        .toBuilder()
+                        .validDuration(Duration.parse(dto.getValidDuration()))
+                        .rateLimitDuration(Duration.parse(dto.getRateLimitDuration()))
+                        .rateLimitHits(dto.getRateLimitHits())
+                        .denyList(dto.getDenyList())
+                        .build();
+    }
+
+    private IndexSettingsRetrieval fromDTO(IndexSettingsRetrievalDTO dto, IndexSettingsRetrieval retrieval) {
+        return
+                retrieval
+                        .toBuilder()
+                        .rateLimitWait(Duration.parse(dto.getRateLimitWait()))
+                        .timeout(Duration.parse(dto.getTimeout()))
+                        .build();
+    }
+
+    public IndexSettings fromUpdateDTO(IndexSettingsUpdateDTO dto, IndexSettings indexSettings) {
+        return
+                indexSettings
+                        .toBuilder()
+                        .ping(fromDTO(dto.getPing(), indexSettings.getPing()))
+                        .retrieval(fromDTO(dto.getRetrieval(), indexSettings.getRetrieval()))
+                        .build();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/settings/IndexSettingsService.java b/src/main/java/nl/dtls/fairdatapoint/service/index/settings/IndexSettingsService.java
new file mode 100644
index 0000000000000000000000000000000000000000..940c105f0dd941e82f49f7b82f764d6a0d29eeda
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/settings/IndexSettingsService.java
@@ -0,0 +1,72 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.settings;
+
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsUpdateDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexSettingsRepository;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettings;
+import nl.dtls.fairdatapoint.service.index.common.RequiredEnabledIndexFeature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.regex.Pattern;
+
+@Service
+@Slf4j
+public class IndexSettingsService {
+
+    @Autowired
+    private IndexSettingsRepository repository;
+
+    @Autowired
+    private IndexSettingsMapper mapper;
+
+    @RequiredEnabledIndexFeature
+    public boolean isPingDenied(PingDTO ping) {
+        log.info("Checking if ping.clientUrl is on deny list: " + ping.getClientUrl());
+        return getOrDefaults().getPing().getDenyList().parallelStream().anyMatch(pattern -> Pattern.matches(pattern, ping.getClientUrl()));
+    }
+
+    @RequiredEnabledIndexFeature
+    public IndexSettings getOrDefaults() {
+        return repository.findFirstBy().orElse(IndexSettings.getDefault());
+    }
+
+    @RequiredEnabledIndexFeature
+    public IndexSettingsDTO getCurrentSettings() {
+        return mapper.toDTO(getOrDefaults());
+    }
+
+    @RequiredEnabledIndexFeature
+    public IndexSettingsDTO updateSettings(IndexSettingsUpdateDTO dto) {
+        return mapper.toDTO(repository.save(mapper.fromUpdateDTO(dto, getOrDefaults())));
+    }
+
+    @RequiredEnabledIndexFeature
+    public IndexSettingsDTO resetSettings() {
+        return updateSettings(mapper.toUpdateDTO(IndexSettings.getDefault()));
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b888d891eb9f10d0983234fb1f28589ae0d4757
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookMapper.java
@@ -0,0 +1,68 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.webhook;
+
+import nl.dtls.fairdatapoint.api.dto.index.webhook.WebhookPayloadDTO;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.WebhookPing;
+import nl.dtls.fairdatapoint.entity.index.event.WebhookTrigger;
+import nl.dtls.fairdatapoint.entity.index.webhook.Webhook;
+import nl.dtls.fairdatapoint.entity.index.webhook.WebhookEvent;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.Instant;
+import java.util.UUID;
+
+@Service
+public class WebhookMapper {
+
+    private static final Integer VERSION = 1;
+
+    public Event toTriggerEvent(Webhook webhook, WebhookEvent webhookEvent, Event triggerEvent) {
+        var webhookTrigger = new WebhookTrigger();
+        webhookTrigger.setWebhook(webhook);
+        webhookTrigger.setMatchedEvent(webhookEvent);
+        return new Event(VERSION, webhookTrigger, triggerEvent);
+    }
+
+    public Event toPingEvent(HttpServletRequest request, Authentication authentication, UUID webhookUuid, String remoteAddr) {
+        var webhookPing = new WebhookPing();
+        webhookPing.setWebhookUuid(webhookUuid);
+        webhookPing.setRemoteAddr(remoteAddr);
+        webhookPing.setTokenName(authentication.getName());
+        return new Event(VERSION, webhookPing);
+    }
+
+    public WebhookPayloadDTO toWebhookPayloadDTO(Event event) {
+        WebhookPayloadDTO webhookPayload = new WebhookPayloadDTO();
+        webhookPayload.setEvent(event.getWebhookTrigger().getMatchedEvent());
+        webhookPayload.setClientUrl(event.getRelatedTo().getClientUrl());
+        webhookPayload.setSecret(event.getWebhookTrigger().getWebhook().getSecret());
+        webhookPayload.setUuid(event.getUuid().toString());
+        webhookPayload.setTimestamp(Instant.now().toString());
+        return webhookPayload;
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookService.java b/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab1ea736a24d6b477df7795dccba3218452fe884
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookService.java
@@ -0,0 +1,149 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.webhook;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import nl.dtls.fairdatapoint.api.dto.index.webhook.WebhookPayloadDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.EventRepository;
+import nl.dtls.fairdatapoint.database.mongo.repository.WebhookRepository;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.webhook.Webhook;
+import nl.dtls.fairdatapoint.entity.index.webhook.WebhookEvent;
+import nl.dtls.fairdatapoint.service.UtilityService;
+import nl.dtls.fairdatapoint.service.index.common.RequiredEnabledIndexFeature;
+import nl.dtls.fairdatapoint.service.index.settings.IndexSettingsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Optional;
+import java.util.UUID;
+
+
+@Service
+public class WebhookService {
+    private static final Logger logger = LoggerFactory.getLogger(WebhookService.class);
+
+    @Autowired
+    private WebhookMapper webhookMapper;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @Autowired
+    WebhookRepository webhookRepository;
+
+    @Autowired
+    EventRepository eventRepository;
+
+    @Autowired
+    private IndexSettingsService indexSettingsService;
+
+    @Autowired
+    private UtilityService utilityService;
+
+    private static final String SECRET_PLACEHOLDER = "*** HIDDEN ***";
+
+    @RequiredEnabledIndexFeature
+    public void processWebhookTrigger(Event event) {
+        var retrievalSettings = indexSettingsService.getOrDefaults().getRetrieval();
+        event.execute();
+        eventRepository.save(event);
+        WebhookPayloadDTO webhookPayload = webhookMapper.toWebhookPayloadDTO(event);
+        try {
+            String payloadWithSecret = objectMapper.writeValueAsString(webhookPayload);
+            String signature = WebhookUtils.computeHashSignature(payloadWithSecret);
+            webhookPayload.setSecret(SECRET_PLACEHOLDER);
+            String payloadWithoutSecret = objectMapper.writeValueAsString(webhookPayload);
+            WebhookUtils.postWebhook(event, retrievalSettings.getTimeout(), payloadWithoutSecret, signature);
+        } catch (JsonProcessingException e) {
+            logger.error("Failed to convert webhook payload to string");
+        } catch (NoSuchAlgorithmException e) {
+            logger.error("Could not compute SHA-1 signature of payload");
+        }
+        event.finish();
+        eventRepository.save(event);
+    }
+
+    @Async
+    @RequiredEnabledIndexFeature
+    public void triggerWebhook(Webhook webhook, WebhookEvent webhookEvent, Event triggerEvent) {
+        Event event = webhookMapper.toTriggerEvent(webhook, webhookEvent, triggerEvent);
+        processWebhookTrigger(event);
+    }
+
+    @Async
+    @RequiredEnabledIndexFeature
+    public void triggerWebhooks(WebhookEvent webhookEvent, Event triggerEvent) {
+        logger.info("Triggered webhook event {} by event {}", webhookEvent, triggerEvent.getUuid());
+        WebhookUtils.filterMatching(webhookRepository.findAll(), webhookEvent, triggerEvent).forEach(webhook -> triggerWebhook(webhook, webhookEvent, triggerEvent));
+    }
+
+    @RequiredEnabledIndexFeature
+    public Event handleWebhookPing(HttpServletRequest request, UUID webhookUuid) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Optional<Webhook> webhook = webhookRepository.findByUuid(webhookUuid);
+        Event event = eventRepository.save(webhookMapper.toPingEvent(request, authentication, webhookUuid, utilityService.getRemoteAddr(request)));
+        if (webhook.isEmpty()) {
+            throw new ResourceNotFoundException("There is no such webhook: " + webhookUuid);
+        }
+        return event;
+    }
+
+    @Async
+    @RequiredEnabledIndexFeature
+    public void triggerWebhooks(Event triggerEvent) {
+        switch (triggerEvent.getType()) {
+            case AdminTrigger:
+                triggerWebhooks(WebhookEvent.AdminTrigger, triggerEvent);
+                break;
+            case IncomingPing:
+                triggerWebhooks(WebhookEvent.IncomingPing, triggerEvent);
+                if (triggerEvent.getIncomingPing().getNewEntry()) {
+                    triggerWebhooks(WebhookEvent.NewEntry, triggerEvent);
+                }
+                break;
+            case MetadataRetrieval:
+                switch (triggerEvent.getRelatedTo().getState()) {
+                    case Valid -> triggerWebhooks(WebhookEvent.EntryValid, triggerEvent);
+                    case Invalid -> triggerWebhooks(WebhookEvent.EntryInvalid, triggerEvent);
+                    case Unreachable -> triggerWebhooks(WebhookEvent.EntryUnreachable, triggerEvent);
+                    default -> logger.warn("Invalid state of MetadataRetrieval: {}", triggerEvent.getRelatedTo().getState());
+                }
+                break;
+            case WebhookPing:
+                triggerWebhooks(WebhookEvent.WebhookPing, triggerEvent);
+                break;
+            default:
+                logger.warn("Invalid event type for webhook trigger: {}", triggerEvent.getType());
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookUtils.java b/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae263aa5164d6d28fe7778093e0d2194153c58bb
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/index/webhook/WebhookUtils.java
@@ -0,0 +1,101 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.webhook;
+
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.http.Exchange;
+import nl.dtls.fairdatapoint.entity.index.http.ExchangeDirection;
+import nl.dtls.fairdatapoint.entity.index.http.ExchangeState;
+import nl.dtls.fairdatapoint.entity.index.webhook.Webhook;
+import nl.dtls.fairdatapoint.entity.index.webhook.WebhookEvent;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class WebhookUtils {
+
+    private static final HttpClient client = HttpClient.newBuilder()
+            .version(HttpClient.Version.HTTP_2)
+            .followRedirects(HttpClient.Redirect.ALWAYS)
+            .build();
+
+    private static boolean webhookMatches(Webhook webhook, WebhookEvent webhookEvent, Event triggerEvent) {
+        boolean matchEvent = webhook.isAllEvents() || webhook.getEvents().contains(webhookEvent);
+        boolean matchEntry =
+                webhook.isAllEntries() || triggerEvent.getRelatedTo() == null || webhook.getEntries().contains(triggerEvent.getRelatedTo().getClientUrl());
+        return matchEvent && matchEntry && webhook.isEnabled();
+    }
+
+    public static Stream<Webhook> filterMatching(List<Webhook> webhooks, WebhookEvent webhookEvent,
+                                                 Event triggerEvent) {
+        return webhooks.parallelStream().filter(webhook -> WebhookUtils.webhookMatches(webhook, webhookEvent,
+                triggerEvent));
+    }
+
+    public static String computeHashSignature(String value) throws NoSuchAlgorithmException {
+        MessageDigest digest = MessageDigest.getInstance("SHA-1");
+        digest.reset();
+        digest.update(value.getBytes(StandardCharsets.UTF_8));
+        return String.format("sha1=%040x", new BigInteger(1, digest.digest()));
+    }
+
+    public static void postWebhook(Event event, Duration timeout, String payload, String signature) {
+        var ex = new Exchange(ExchangeDirection.OUTGOING);
+        event.getWebhookTrigger().setExchange(ex);
+        try {
+            HttpRequest request = HttpRequest.newBuilder()
+                    .uri(URI.create(event.getWebhookTrigger().getWebhook().getPayloadUrl()))
+                    .timeout(timeout)
+                    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
+                    .header("X-Signature", signature)
+                    .POST(HttpRequest.BodyPublishers.ofString(payload))
+                    .build();
+            ex.getRequest().setFromHttpRequest(request);
+            ex.setState(ExchangeState.Requested);
+            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+            ex.getResponse().setFromHttpResponse(response);
+            ex.setState(ExchangeState.Retrieved);
+        } catch (InterruptedException e) {
+            ex.setState(ExchangeState.Timeout);
+            ex.setError("Timeout");
+        } catch (IllegalArgumentException e) {
+            ex.setState(ExchangeState.Failed);
+            ex.setError("Invalid URI: " + e.getMessage());
+        } catch (IOException e) {
+            ex.setState(ExchangeState.Failed);
+            ex.setError("IO error: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/jwt/JwtService.java b/src/main/java/nl/dtls/fairdatapoint/service/jwt/JwtService.java
index 5784ab17364f66eee9d6c2c5c5fc909161b6b7ec..a6449c931af33f1413509258bf8ea57e75e588a3 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/jwt/JwtService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/jwt/JwtService.java
@@ -30,7 +30,6 @@ import nl.dtls.fairdatapoint.entity.user.User;
 import nl.dtls.fairdatapoint.service.security.MongoAuthenticationService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpHeaders;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -38,14 +37,12 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
-import javax.servlet.http.HttpServletRequest;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.Key;
 import java.util.Base64;
 import java.util.Date;
 import java.util.Optional;
 
-import static java.util.Optional.of;
-import static java.util.Optional.ofNullable;
-
 @Component
 public class JwtService {
 
@@ -64,9 +61,15 @@ public class JwtService {
     @Autowired
     private MongoAuthenticationService mongoAuthenticationService;
 
+    private JwtParser parser;
+
+    private Key key;
+
     @PostConstruct
     protected void init() {
         secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
+        key = new SecretKeySpec(secretKey.getBytes(), SignatureAlgorithm.HS256.getJcaName());
+        parser = Jwts.parserBuilder().setSigningKey(key).build();
     }
 
     public String createToken(AuthDTO authDTO) {
@@ -85,21 +88,15 @@ public class JwtService {
     }
 
     public String getUserUuid(String token) {
-        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
-    }
-
-    public String resolveToken(HttpServletRequest req) {
-        return ofNullable(req.getHeader(HttpHeaders.AUTHORIZATION))
-                .filter(h -> h.startsWith("Bearer "))
-                .flatMap(h -> of(h.substring(7)))
-                .orElse(null);
+        return parser.parseClaimsJws(token).getBody().getSubject();
     }
 
     public boolean validateToken(String token) {
         try {
-            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
+            Jws<Claims> claims = parser.parseClaimsJws(token);
             return !claims.getBody().getExpiration().before(new Date());
         } catch (JwtException | IllegalArgumentException e) {
+            System.out.println(e);
             throw new UnauthorizedException("Expired or invalid JWT token");
         }
     }
@@ -112,7 +109,7 @@ public class JwtService {
                 .setClaims(claims)
                 .setIssuedAt(now)
                 .setExpiration(validity)
-                .signWith(SignatureAlgorithm.HS256, secretKey)
+                .signWith(key)
                 .compact();
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/label/LabelService.java b/src/main/java/nl/dtls/fairdatapoint/service/label/LabelService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e71d664358c54ab86744722a768e2763a2ea0677
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/label/LabelService.java
@@ -0,0 +1,91 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.label;
+
+import static java.util.function.Predicate.isEqual;
+import static nl.dtls.fairdatapoint.config.CacheConfig.LABEL_CACHE;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import com.github.fairdevkit.rdf.resolver.api.ResourceResolver;
+import com.github.fairdevkit.rdf.resolver.core.ContentNegotiationStrategy;
+import com.github.fairdevkit.rdf.resolver.core.CoreResourceResolver;
+import com.github.fairdevkit.rdf.resolver.core.PathExtensionStrategy;
+import java.util.Optional;
+
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.api.dto.label.LabelDTO;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Literal;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.util.Models;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+import org.eclipse.rdf4j.model.vocabulary.SKOS;
+import org.springframework.cache.annotation.CacheConfig;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@CacheConfig(cacheNames = LABEL_CACHE)
+public class LabelService {
+    private final ResourceResolver resolver;
+
+    public LabelService() {
+        var resolver = new CoreResourceResolver();
+        resolver.register(new ContentNegotiationStrategy());
+        resolver.register(new PathExtensionStrategy());
+
+        this.resolver = resolver;
+    }
+
+    @Cacheable
+    public Optional<LabelDTO> getLabel(String iri, String lang) {
+        try {
+            var subject = i(iri);
+
+            return resolver.resolveResource(iri)
+                    .flatMap(model -> getPropertyLiteralByLanguage(model, subject, SKOS.PREF_LABEL, lang)
+                            .or(() -> getPropertyLiteralWithoutLanguage(model, subject, SKOS.PREF_LABEL))
+                            .or(() -> getPropertyLiteralByLanguage(model, subject, RDFS.LABEL, lang))
+                            .or(() -> getPropertyLiteralWithoutLanguage(model, subject, RDFS.LABEL))
+                    )
+                    .map(literal -> new LabelDTO(literal.getLabel(), literal.getLanguage().orElse("")));
+        } catch (Exception e) {
+            log.warn("Unable to resolve label for {} (lang {}): {}", iri, lang, e.getMessage());
+            return Optional.empty();
+        }
+    }
+
+    private static Optional<Literal> getPropertyLiteralByLanguage(Model model, IRI subject, IRI predicate, String lang) {
+        return Models.getPropertyLiterals(model, subject, predicate)
+                .stream()
+                .filter(literal -> literal.getLanguage().filter(isEqual(lang)).isPresent())
+                .findFirst();
+    }
+
+    private static Optional<Literal> getPropertyLiteralWithoutLanguage(Model model, IRI subject, IRI predicate) {
+        return Models.getPropertyLiterals(model, subject, predicate)
+                .stream()
+                .filter(literal -> literal.getLanguage().isEmpty())
+                .findFirst();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/membership/MembershipService.java b/src/main/java/nl/dtls/fairdatapoint/service/membership/MembershipService.java
index 7c0c10020639abb5d0f11d9613f037c65c64d4ce..e42b03cad4d255d226b6804d3ca08532dddda27e 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/membership/MembershipService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/membership/MembershipService.java
@@ -25,6 +25,7 @@ package nl.dtls.fairdatapoint.service.membership;
 import nl.dtls.fairdatapoint.api.dto.membership.MembershipDTO;
 import nl.dtls.fairdatapoint.database.mongo.repository.MembershipRepository;
 import nl.dtls.fairdatapoint.entity.membership.Membership;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -49,4 +50,50 @@ public class MembershipService {
                         .map(membershipMapper::toDTO)
                         .collect(toList());
     }
+
+    public void addToMembership(ResourceDefinition resourceDefinition) {
+        String rdUuid = resourceDefinition.getUuid();
+
+        // Add to owner
+        Membership owner = membershipRepository.findByUuid("49f2bcfd-ef0a-4a3a-a1a3-0fc72a6892a8").get();
+        addEntityIfMissing(owner, rdUuid);
+        membershipRepository.save(owner);
+
+        // Add to data provider
+        if (resourceDefinition.getUrlPrefix().equals("catalog")) {
+            Membership dataProvider = membershipRepository.findByUuid("87a2d984-7db2-43f6-805c-6b0040afead5").get();
+            addEntityIfMissing(dataProvider, rdUuid);
+            membershipRepository.save(dataProvider);
+        }
+    }
+
+    public void removeFromMembership(ResourceDefinition resourceDefinition) {
+        String rdUuid = resourceDefinition.getUuid();
+
+        // Add to owner
+        Membership owner = membershipRepository.findByUuid("49f2bcfd-ef0a-4a3a-a1a3-0fc72a6892a8").get();
+        removeEntityIfPresent(owner, rdUuid);
+        membershipRepository.save(owner);
+
+        // Add to data provider
+        if (resourceDefinition.getUrlPrefix().equals("catalog")) {
+            Membership dataProvider = membershipRepository.findByUuid("87a2d984-7db2-43f6-805c-6b0040afead5").get();
+            removeEntityIfPresent(dataProvider, rdUuid);
+            membershipRepository.save(dataProvider);
+        }
+    }
+
+    private void addEntityIfMissing(Membership membership, String rdUuid) {
+        if (!membership.getAllowedEntities().contains(rdUuid)) {
+            membership.getAllowedEntities().add(rdUuid);
+        }
+    }
+
+    private void removeEntityIfPresent(Membership membership, String rdUuid) {
+        int index = membership.getAllowedEntities().indexOf(rdUuid);
+        if (index != -1) {
+            membership.getAllowedEntities().remove(index);
+        }
+    }
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataService.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataService.java
index 50921e8c444df186057b38497496399617762761..2001c5a5712f2e20475a737392a62d27fdcedd83 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataService.java
@@ -25,6 +25,7 @@ package nl.dtls.fairdatapoint.service.metadata.catalog;
 import lombok.extern.slf4j.Slf4j;
 import nl.dtls.fairdatapoint.database.rdf.repository.catalog.CatalogMetadataRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import nl.dtls.fairdatapoint.service.metadata.common.AbstractMetadataService;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
 import org.eclipse.rdf4j.model.IRI;
@@ -34,9 +35,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Nonnull;
-import java.util.*;
+import java.util.List;
 
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getThemeTaxonomies;
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataSetter.setThemeTaxonomies;
 
 @Service("catalogMetadataService")
@@ -52,14 +52,23 @@ public class CatalogMetadataService extends AbstractMetadataService {
         Model catalog = super.retrieve(uri);
         try {
             List<IRI> themes = metadataRepository.getDatasetThemesForCatalog(uri);
-            Set<IRI> set = new TreeSet<>(Comparator.comparing(IRI::toString));
-            set.addAll(getThemeTaxonomies(catalog));
-            set.addAll(themes);
-            setThemeTaxonomies(catalog, uri, new ArrayList<>(set));
+            setThemeTaxonomies(catalog, uri, themes);
         } catch (MetadataRepositoryException ex) {
             log.error("Error retrieving the metadata");
             throw new MetadataServiceException(ex.getMessage());
         }
         return catalog;
     }
+
+    @Override
+    public Model store(Model metadata, IRI uri, ResourceDefinition resourceDefinition) throws MetadataServiceException {
+        setThemeTaxonomies(metadata, uri, null);
+        return super.store(metadata, uri, resourceDefinition);
+    }
+
+    @Override
+    public Model update(Model metadata, IRI uri, ResourceDefinition rd) throws MetadataServiceException {
+        setThemeTaxonomies(metadata, uri, null);
+        return super.update(metadata, uri, rd);
+    }
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/common/AbstractMetadataService.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/common/AbstractMetadataService.java
index 37e0fee01afc09f2aea230ad8abc91eb7527aa86..43cc9729e1da4501bdd63ed454a7a074f125f024 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadata/common/AbstractMetadataService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/common/AbstractMetadataService.java
@@ -22,20 +22,22 @@
  */
 package nl.dtls.fairdatapoint.service.metadata.common;
 
-import com.google.common.base.Preconditions;
 import lombok.extern.slf4j.Slf4j;
-import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.common.MetadataRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
 import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
 import nl.dtls.fairdatapoint.entity.metadata.Metadata;
 import nl.dtls.fairdatapoint.entity.metadata.MetadataGetter;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
 import nl.dtls.fairdatapoint.entity.user.User;
 import nl.dtls.fairdatapoint.service.member.MemberService;
 import nl.dtls.fairdatapoint.service.metadata.enhance.MetadataEnhancer;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
+import nl.dtls.fairdatapoint.service.metadata.state.MetadataStateService;
 import nl.dtls.fairdatapoint.service.metadata.validator.MetadataValidator;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
 import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
 import nl.dtls.fairdatapoint.service.user.CurrentUserService;
 import nl.dtls.fairdatapoint.vocabulary.FDP;
@@ -47,13 +49,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.access.prepost.PreAuthorize;
 
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
+import java.time.OffsetDateTime;
+import java.util.*;
 import java.util.stream.Collectors;
 
+import static java.lang.String.format;
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getChildren;
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getParent;
 import static nl.dtls.fairdatapoint.util.ThrowingFunction.suppress;
@@ -73,31 +73,34 @@ public abstract class AbstractMetadataService implements MetadataService {
     private CurrentUserService currentUserService;
 
     @Autowired
-    protected ResourceDefinitionRepository resourceDefinitionRepository;
+    protected MetadataEnhancer metadataEnhancer;
 
     @Autowired
-    protected ResourceDefinitionService resourceDefinitionService;
+    protected MetadataValidator metadataValidator;
 
     @Autowired
-    protected MetadataEnhancer metadataEnhancer;
+    private ResourceDefinitionCache resourceDefinitionCache;
 
     @Autowired
-    protected MetadataValidator metadataValidator;
+    private MetadataStateService metadataStateService;
+
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
 
     @Override
     public Model retrieve(IRI uri) throws MetadataServiceException, ResourceNotFoundException {
-        Model metadata = new LinkedHashModel();
         try {
-            Preconditions.checkNotNull(uri, "Resource uri not be null.");
-            List<Statement> statements = metadataRepository.retrieveResource(uri);
+            // 1. Get metadata
+            List<Statement> statements = metadataRepository.find(uri);
             if (statements.isEmpty()) {
-                String msg = ("No metadata found for the uri : " + uri);
-                throw new ResourceNotFoundException(msg);
+                throw new ResourceNotFoundException(format("No metadata found for the uri '%s'", uri));
             }
+
+            // 2. Convert to model
+            Model metadata = new LinkedHashModel();
             metadata.addAll(statements);
             return metadata;
         } catch (MetadataRepositoryException ex) {
-            log.error("Error retrieving the metadata");
             throw new MetadataServiceException(ex.getMessage());
         }
     }
@@ -116,12 +119,12 @@ public abstract class AbstractMetadataService implements MetadataService {
         try {
             metadataValidator.validate(metadata, uri, resourceDefinition);
             metadataEnhancer.enhance(metadata, uri, resourceDefinition);
-            metadataRepository.storeStatements(new ArrayList<>(metadata), uri);
+            metadataRepository.save(new ArrayList<>(metadata), uri);
             updateParent(metadata, uri, resourceDefinition);
             addPermissions(uri);
+            addState(uri);
             return metadata;
         } catch (MetadataRepositoryException e) {
-            log.error("Error storing distribution metadata");
             throw new MetadataServiceException(e.getMessage());
         }
     }
@@ -129,75 +132,81 @@ public abstract class AbstractMetadataService implements MetadataService {
     @Override
     @PreAuthorize("hasPermission(#uri.stringValue(), 'nl.dtls.fairdatapoint.entity.metadata.Metadata', 'WRITE') " +
             "or hasRole('ADMIN')")
-    public Model update(Model metadata, IRI uri, ResourceDefinition resourceDefinition) throws MetadataServiceException {
+    public Model update(Model metadata, IRI uri, ResourceDefinition rd) throws MetadataServiceException {
         try {
-            metadataValidator.validate(metadata, uri, resourceDefinition);
+            metadataValidator.validate(metadata, uri, rd);
             Model oldMetadata = retrieve(uri);
-            metadataEnhancer.enhance(metadata, uri, resourceDefinition, oldMetadata);
-            metadataRepository.removeResource(uri);
-            metadataRepository.storeStatements(new ArrayList<>(metadata), uri);
-            updateParent(metadata, uri, resourceDefinition);
+            metadataEnhancer.enhance(metadata, uri, rd, oldMetadata);
+            metadataRepository.remove(uri);
+            metadataRepository.save(new ArrayList<>(metadata), uri);
+            updateParent(metadata, uri, rd);
             return metadata;
         } catch (MetadataRepositoryException | MetadataServiceException e) {
-            log.error("Error updating metadata");
             throw (new MetadataServiceException(e.getMessage()));
         }
     }
 
     @Override
     @PreAuthorize("hasRole('ADMIN')")
-    public void delete(IRI uri, ResourceDefinition resourceDefinition) throws MetadataServiceException {
+    public void delete(IRI uri, ResourceDefinition rd) throws MetadataServiceException {
         try {
             Model metadata = retrieve(uri);
-            String childPredicate = resourceDefinition.getChild();
 
             // Delete all children
-            if (childPredicate != null) {
-                String childRdUuid = resourceDefinition.getChildResourceDefinitionUuid();
-                ResourceDefinition childRd = resourceDefinitionService.getByUuid(childRdUuid);
-                List<IRI> children = getChildren(metadata, i(childPredicate));
-                for (IRI child : children) {
-                    delete(child, childRd);
+            for (ResourceDefinitionChild child : rd.getChildren()) {
+                String childRdUuid = child.getResourceDefinitionUuid();
+                ResourceDefinition rdChild = resourceDefinitionCache.getByUuid(childRdUuid);
+                if (rdChild != null) {
+                    List<IRI> children = getChildren(metadata, i(child.getRelationUri()));
+                    for (IRI childUri : children) {
+                        delete(childUri, rdChild);
+                    }
                 }
             }
 
             // Remove reference at parent
-            String parentRdUuid = resourceDefinition.getParentResourceDefinitionUuid();
-            if (parentRdUuid != null) {
-                ResourceDefinition parentRd = resourceDefinitionService.getByUuid(parentRdUuid);
+            Set<ResourceDefinition> rdParents = resourceDefinitionCache.getParentsByUuid(rd.getUuid());
+            // select parent based on URI prefix
+            for (ResourceDefinition rdParent : rdParents) {
                 IRI parentUri = getParent(metadata);
-                Model parent = retrieve(parentUri);
-                parent.remove(null, i(parentRd.getChild()), uri);
-                update(parent, parentUri, parentRd);
+                Model parentMetadata = retrieve(parentUri);
+                for (ResourceDefinitionChild rdChild : rdParent.getChildren()) {
+                    if (rdChild.getResourceDefinitionUuid().equals(rd.getUuid())) {
+                        parentMetadata.remove(null, i(rdChild.getRelationUri()), uri);
+                        update(parentMetadata, parentUri, rdParent);
+                    }
+                }
             }
 
             // Delete itself
-            metadataRepository.removeResource(uri);
+            metadataRepository.remove(uri);
 
         } catch (MetadataRepositoryException | MetadataServiceException e) {
-            log.error("Error updating metadata");
             throw (new MetadataServiceException(e.getMessage()));
         }
     }
 
-    protected void updateParent(Model metadata, IRI uri, ResourceDefinition resourceDefinition) throws MetadataServiceException {
+    protected void updateParent(Model metadata, IRI uri, ResourceDefinition rd) throws MetadataServiceException {
         IRI parent = MetadataGetter.getParent(metadata);
         if (parent != null) {
-            String parentRdUuid = resourceDefinition.getParentResourceDefinitionUuid();
-            ResourceDefinition parentResourceDefinition = resourceDefinitionService.getByUuid(parentRdUuid);
-            try {
-                List<Statement> statements = new ArrayList<>();
-                if (parentResourceDefinition.getChild() != null) {
-                    statements.add(s(parent, i(parentResourceDefinition.getChild()), uri));
+            ResourceDefinition rdParent = resourceDefinitionService.getByUrl(parent.toString());
+            if (rdParent != null) {
+                try {
+                    List<Statement> statements = new ArrayList<>();
+                    for (ResourceDefinitionChild rdChild : rdParent.getChildren()) {
+                        if (rdChild.getResourceDefinitionUuid().equals(rd.getUuid())) {
+                            statements.add(s(parent, i(rdChild.getRelationUri()), uri));
+                        }
+                    }
+                    metadataRepository.removeStatement(parent, FDP.METADATAMODIFIED, null, parent);
+                    statements.add(s(parent, FDP.METADATAMODIFIED, l(OffsetDateTime.now())));
+                    metadataRepository.save(statements, parent);
+                } catch (MetadataRepositoryException e) {
+                    throw new MetadataServiceException("Problem with updating parent timestamp");
                 }
-                metadataRepository.removeStatement(parent, FDP.METADATAMODIFIED, null);
-                statements.add(s(parent, FDP.METADATAMODIFIED, l(LocalDateTime.now())));
-                metadataRepository.storeStatements(statements, parent);
-            } catch (MetadataRepositoryException e) {
-                throw new MetadataServiceException("Problem with updating parent timestamp");
+                Model parentMetadata = retrieve(parent);
+                updateParent(parentMetadata, parent, rdParent);
             }
-            Model parentMetadata = retrieve(parent);
-            updateParent(parentMetadata, parent, parentResourceDefinition);
         }
     }
 
@@ -209,4 +218,9 @@ public abstract class AbstractMetadataService implements MetadataService {
         User user = oUser.get();
         memberService.createOwner(uri.stringValue(), Metadata.class, user.getUuid());
     }
+
+    private void addState(IRI uri) {
+        metadataStateService.initState(uri);
+    }
+
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/enhance/MetadataEnhancer.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/enhance/MetadataEnhancer.java
index f605e04359a895f7d1b0c4bb6f3b8294dcda5259..a9121d320294405327d3e21007e654d0509aee26 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadata/enhance/MetadataEnhancer.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/enhance/MetadataEnhancer.java
@@ -22,25 +22,32 @@
  */
 package nl.dtls.fairdatapoint.service.metadata.enhance;
 
-import nl.dtls.fairdatapoint.entity.metadata.Agent;
 import nl.dtls.fairdatapoint.entity.metadata.Identifier;
-import nl.dtls.fairdatapoint.entity.metadata.MetadataSetter;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
-import nl.dtls.fairdatapoint.service.metadatametrics.FairMetadataMetricsService;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import nl.dtls.fairdatapoint.service.metadata.metric.MetricsMetadataService;
+import nl.dtls.fairdatapoint.service.profile.ProfileService;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
+import nl.dtls.fairdatapoint.util.ValueFactoryHelper;
 import nl.dtls.fairdatapoint.vocabulary.DATACITE;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
-import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
-import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
 
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getIssued;
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.*;
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataSetter.*;
 import static nl.dtls.fairdatapoint.util.RdfUtil.containsObject;
+import static nl.dtls.fairdatapoint.util.RdfUtil.getObjectsBy;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.l;
 
@@ -59,33 +66,45 @@ public class MetadataEnhancer {
     private IRI license;
 
     @Autowired
-    @Qualifier("publisher")
-    private Agent publisher;
+    private MetricsMetadataService metricsMetadataService;
 
     @Autowired
-    private FairMetadataMetricsService fmMetricsService;
+    private ProfileService profileService;
 
-    public void enhance(Model metadata, IRI uri, ResourceDefinition resourceDefinition, Model oldMetadata) {
-        enhance(metadata, uri, resourceDefinition);
-        setIssued(metadata, uri, l(getIssued(oldMetadata)));
-    }
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
+
+    public void enhance(Model metadata, IRI uri, ResourceDefinition rd, Model oldMetadata) {
+        enhance(metadata, uri, rd);
 
-    public void enhance(Model metadata, IRI uri, ResourceDefinition resourceDefinition) {
-        addDefaultValues(metadata, uri, resourceDefinition);
-        setSpecification(metadata, uri, resourceDefinition);
-        setTimestamps(metadata, uri);
+        // Populate with current data from the triple store
+        setIssued(metadata, uri, l(getIssued(oldMetadata)));
+        if (rd.getUrlPrefix().equals("catalog")) {
+            setMetadataIssued(metadata, uri, l(getMetadataIssued(oldMetadata)));
+        }
     }
 
-    private void addDefaultValues(Model metadata, IRI uri, ResourceDefinition resourceDefinition) {
+    public void enhance(Model metadata, IRI uri, ResourceDefinition rd) {
         // Add RDF Type
-        setRdfTypes(metadata, uri, i(resourceDefinition.getRdfType()), i("http://www.w3.org/ns/dcat#Resource"));
-
-        // Add PID
-        setMetadataIdentifier(metadata, uri, createMetadataIdentifier(uri));
+        List<IRI> targetClassUris = resourceDefinitionService.getTargetClassUris(rd)
+                .stream()
+                .map(ValueFactoryHelper::i)
+                .collect(Collectors.toList());
+        setRdfTypes(metadata, uri, targetClassUris);
+
+        // Add identifiers
+        Identifier identifier = createMetadataIdentifier(uri);
+        setMetadataIdentifier(metadata, uri, identifier);
+        if (rd.getUrlPrefix().equals("")) {
+            setRepositoryIdentifier(metadata, uri, identifier);
+        }
 
-        // Add default publisher
-        if (!containsObject(metadata, uri.stringValue(), DCTERMS.PUBLISHER.stringValue()) && publisher != null) {
-            setPublisher(metadata, uri, publisher);
+        // Add label
+        if (containsObject(metadata, uri.stringValue(), DCTERMS.TITLE.stringValue())) {
+            setLabel(metadata, uri, getTitle(metadata));
         }
 
         // Add default language
@@ -105,20 +124,41 @@ public class MetadataEnhancer {
         }
 
         // Add FAIR metrics
-        setMetrics(metadata, uri, fmMetricsService.getMetrics(uri));
+        setMetrics(metadata, uri, metricsMetadataService.generateMetrics(uri));
+
+        // Add timestamps
+        OffsetDateTime timestamp = OffsetDateTime.now();
+        setIssued(metadata, uri, l(timestamp));
+        setModified(metadata, uri, l(timestamp));
+        if (rd.getUrlPrefix().equals("catalog")) {
+            setMetadataIssued(metadata, uri, l(timestamp));
+            setMetadataModified(metadata, uri, l(timestamp));
+        }
     }
 
-    private void setSpecification(Model metadata, IRI uri, ResourceDefinition rd) {
-        MetadataSetter.setSpecification(metadata, uri, i(rd.getSpecs()));
+    public void enhanceWithLinks(IRI entityUri, Model entity, ResourceDefinition rd, String persistentUrl,
+                                 Model resultRdf) {
+        for (ResourceDefinitionChild child : rd.getChildren()) {
+            ResourceDefinition rdChild = resourceDefinitionCache.getByUuid(child.getResourceDefinitionUuid());
+            IRI container = i(format("%s/%s/", persistentUrl, rdChild.getUrlPrefix()));
+
+            resultRdf.add(container, RDF.TYPE, LDP.DIRECT_CONTAINER);
+            resultRdf.add(container, DCTERMS.TITLE, l(child.getListView().getTitle()));
+            resultRdf.add(container, LDP.MEMBERSHIP_RESOURCE, entityUri);
+            resultRdf.add(container, LDP.HAS_MEMBER_RELATION, i(child.getRelationUri()));
+            for (org.eclipse.rdf4j.model.Value childUri : getObjectsBy(entity, entityUri, i(child.getRelationUri()))) {
+                resultRdf.add(container, LDP.CONTAINS, i(childUri.stringValue()));
+            }
+        }
     }
 
-    private void setTimestamps(Model metadata, IRI uri) {
-        setIssued(metadata, uri, l(LocalDateTime.now()));
-        setModified(metadata, uri, l(LocalDateTime.now()));
+    public void enhanceWithResourceDefinition(IRI entityUri, ResourceDefinition rd, Model resultRdf) {
+        resultRdf.add(entityUri, DCTERMS.CONFORMS_TO, profileService.getProfileUri(rd));
+        resultRdf.add(profileService.getProfileUri(rd), RDFS.LABEL, l(format("%s Profile", rd.getName())));
     }
 
     private Identifier createMetadataIdentifier(IRI uri) {
-        IRI identifierUri = i(uri.stringValue() + "/#identifier");
+        IRI identifierUri = i(uri.stringValue() + "#identifier");
         return new Identifier(identifierUri, DATACITE.IDENTIFIER, l(uri));
     }
 
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataService.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataService.java
index 7d26d2e356c5c12fc37a4bc7762124487f04c69c..fb76164997ba0e3527dc90b6fe31b1cc339ee9cf 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataService.java
@@ -23,6 +23,7 @@
 package nl.dtls.fairdatapoint.service.metadata.generic;
 
 import nl.dtls.fairdatapoint.entity.exception.ForbiddenException;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
 import nl.dtls.fairdatapoint.entity.metadata.Metadata;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import nl.dtls.fairdatapoint.entity.user.UserRole;
@@ -45,7 +46,7 @@ public class GenericMetadataService extends AbstractMetadataService {
         if (!rd.getName().equals("Repository")) {
             // 1. Check permissions
             String parentId = Optional.ofNullable(getParent(metadata))
-                    .orElseThrow(() -> new MetadataServiceException("Metadata has no parent")).stringValue();
+                    .orElseThrow(() -> new ValidationException("Metadata has no parent")).stringValue();
             if (!(memberService.checkPermission(parentId, Metadata.class, BasePermission.CREATE) || memberService.checkRole(UserRole.ADMIN))) {
                 throw new ForbiddenException("You are not allow to add new entry");
             }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/metric/MetricsMetadataService.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/metric/MetricsMetadataService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ba76a5a22456e7dab9eaf506f03d9724165a877
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/metric/MetricsMetadataService.java
@@ -0,0 +1,66 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/*
+ * 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 nl.dtls.fairdatapoint.service.metadata.metric;
+
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.entity.metadata.Metric;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.service.settings.SettingsService;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.eclipse.rdf4j.model.IRI;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+@Slf4j
+@Service
+public class MetricsMetadataService {
+
+    @Autowired
+    private SettingsService settingsService;
+
+    public List<Metric> generateMetrics(@Nonnull IRI metadataURI) {
+        Settings settings = settingsService.getOrDefaults();
+        return settings.getMetadataMetrics().stream()
+                .map(entry ->
+                        new Metric(
+                                i(format("%s/metrics/%s", metadataURI.toString(), DigestUtils.md5Hex(entry.getMetricUri()))),
+                                i(entry.getResourceUri()),
+                                i(entry.getResourceUri())))
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/state/MetadataStateService.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/state/MetadataStateService.java
new file mode 100644
index 0000000000000000000000000000000000000000..29955f5b18f2df525bd227c486f8f4122a3c9950
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/state/MetadataStateService.java
@@ -0,0 +1,125 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.metadata.state;
+
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import nl.dtls.fairdatapoint.service.metadata.validator.MetadataStateValidator;
+import nl.dtls.fairdatapoint.service.user.CurrentUserService;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
+import static nl.dtls.fairdatapoint.util.RdfUtil.getObjectsBy;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+@Service
+public class MetadataStateService {
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    @Autowired
+    private MetadataStateValidator metadataStateValidator;
+
+    @Autowired
+    private CurrentUserService currentUserService;
+
+    public Metadata get(IRI metadataUri) {
+        Optional<Metadata> oMetadata = metadataRepository.findByUri(metadataUri.stringValue());
+        if (oMetadata.isEmpty()) {
+            throw new ResourceNotFoundException(format("Metadata info '%s' was not found", metadataUri));
+        }
+        return oMetadata.get();
+    }
+
+    public MetaStateDTO getState(IRI metadataUri, Model model, ResourceDefinition rd) {
+        // 1. Return null if user is not log in
+        if (currentUserService.getCurrentUser().isEmpty()) {
+            return null;
+        }
+
+        // 2. Get metadata info for current
+        Optional<Metadata> oMetadata = metadataRepository.findByUri(metadataUri.stringValue());
+        if (oMetadata.isEmpty()) {
+            throw new ResourceNotFoundException(format("Metadata info '%s' was not found", metadataUri));
+        }
+        Metadata metadata = oMetadata.get();
+
+        // 3. Get metadata info for children
+        List<String> childrenUris = new ArrayList<>();
+        for (ResourceDefinitionChild rdChild : rd.getChildren()) {
+            IRI relationUri = i(rdChild.getRelationUri());
+            for (org.eclipse.rdf4j.model.Value childUri : getObjectsBy(model, metadataUri, relationUri)) {
+                childrenUris.add(childUri.stringValue());
+            }
+        }
+        Map<String, MetadataState> children =
+                metadataRepository.findByUriIn(childrenUris)
+                        .stream()
+                        .collect(Collectors.toMap(Metadata::getUri, Metadata::getState));
+
+        // 4. Build response
+        return new MetaStateDTO(
+                metadata.getState(),
+                children
+        );
+    }
+
+    public void initState(IRI metadataUri) {
+        Metadata metadata = new Metadata(null, metadataUri.stringValue(), MetadataState.DRAFT);
+        metadataRepository.save(metadata);
+    }
+
+    public void modifyState(IRI metadataUri, MetaStateChangeDTO reqDto) {
+        // 1. Get metadata info for current
+        Optional<Metadata> oMetadata = metadataRepository.findByUri(metadataUri.stringValue());
+        if (oMetadata.isEmpty()) {
+            throw new ResourceNotFoundException(format("Metadata info '%s' was not found", metadataUri));
+        }
+        Metadata metadata = oMetadata.get();
+
+        // 2. Validate
+        metadataStateValidator.validate(reqDto, metadata);
+
+        // 3. Update
+        metadata.setState(reqDto.getCurrent());
+        metadataRepository.save(metadata);
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataStateValidator.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataStateValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff0c005e8e452a2db2f9b62b6fb6bd217cfe5808
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataStateValidator.java
@@ -0,0 +1,44 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.metadata.validator;
+
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MetadataStateValidator {
+
+    public void validate(MetaStateChangeDTO reqDto, Metadata metadata) {
+        if (reqDto.getCurrent().equals(MetadataState.DRAFT)) {
+            throw new ValidationException("You can not change state to DRAFT");
+        }
+
+        if (metadata.getState().equals(MetadataState.PUBLISHED)) {
+            throw new ValidationException("Metadata is already published");
+        }
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataValidator.java b/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataValidator.java
index 8544bf59cf60be06e7ea7e63c6834f4727fa4f99..b546566093e6eac6a78bac79d0a0cafe7c64a6a3 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataValidator.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/metadata/validator/MetadataValidator.java
@@ -22,13 +22,13 @@
  */
 package nl.dtls.fairdatapoint.service.metadata.validator;
 
-import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.common.MetadataRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
 import nl.dtls.fairdatapoint.entity.exception.ValidationException;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
 import nl.dtls.fairdatapoint.service.rdf.ShaclValidator;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
 import nl.dtls.fairdatapoint.service.shape.ShapeService;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
@@ -37,17 +37,13 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
-import java.util.Optional;
-
+import static java.lang.String.format;
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getParent;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
 @Service
 public class MetadataValidator {
 
-    @Autowired
-    private ResourceDefinitionRepository resourceDefinitionRepository;
-
     @Autowired
     @Qualifier("genericMetadataRepository")
     private MetadataRepository metadataRepository;
@@ -58,9 +54,12 @@ public class MetadataValidator {
     @Autowired
     private ShapeService shapeService;
 
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
+
     public void validate(Model metadata, IRI uri, ResourceDefinition rd) throws MetadataServiceException {
         validateByShacl(metadata, uri);
-        if (!rd.getName().equals("Repository")) {
+        if (!rd.getUrlPrefix().isEmpty()) {
             validateParent(metadata, rd);
         }
     }
@@ -77,16 +76,18 @@ public class MetadataValidator {
             throw new ValidationException("Not parent uri");
         }
 
-        // 2. Check correctness of parent type
+        // 2. Get parent resource definition
+        ResourceDefinition rdParent = resourceDefinitionService.getByUrl(parent.toString());
+        if (rdParent.getChildren().stream().noneMatch(rdChild -> rdChild.getResourceDefinitionUuid().equals(rd.getUuid()))) {
+            throw new ValidationException(format("Parent is not of correct type (RD: %s)", rdParent.getName()));
+        }
+
+        // 3. Check correctness of parent type
         try {
-            String parentRdUuid = rd.getParentResourceDefinitionUuid();
-            if (parentRdUuid != null) {
-                Optional<ResourceDefinition> oParentDefinition = resourceDefinitionRepository.findByUuid(parentRdUuid);
-                if (oParentDefinition.isPresent()) {
-                    ResourceDefinition parentDefinition = oParentDefinition.get();
-                    if (!metadataRepository.isStatementExist(parent, RDF.TYPE, i(parentDefinition.getRdfType()))) {
-                        throw new ValidationException("Parent is not of correct type");
-                    }
+            // select parent based on URI prefix
+            for (String rdfType : resourceDefinitionService.getTargetClassUris(rdParent)) {
+                if (!metadataRepository.checkExistence(parent, RDF.TYPE, i(rdfType))) {
+                    throw new ValidationException(format("Parent is not of type (missing type: %s)", rdfType));
                 }
             }
         } catch (MetadataRepositoryException e) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsServiceImpl.java b/src/main/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsServiceImpl.java
deleted file mode 100755
index c23a757e1dcbce386868bdd2adee9afb3f9c52c6..0000000000000000000000000000000000000000
--- a/src/main/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsServiceImpl.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 DTL
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-/*
- * 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 nl.dtls.fairdatapoint.service.metadatametrics;
-
-import com.google.common.base.Preconditions;
-import nl.dtls.fairdatapoint.entity.metadata.Metric;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.ValueFactory;
-import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Nonnull;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-@Service
-public class FairMetadataMetricsServiceImpl implements FairMetadataMetricsService {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(FairMetadataMetricsServiceImpl.class);
-
-    private static final ValueFactory VALUEFACTORY = SimpleValueFactory.getInstance();
-
-    @Autowired
-    @Qualifier("metadataMetrics")
-    private Map<String, String> metadataMetrics;
-
-    /**
-     * This method returns list of fair metrics for metadata
-     *
-     * @param metadataURI metadata URI
-     * @return List of fair metrics
-     */
-    @Override
-    public List<Metric> getMetrics(@Nonnull IRI metadataURI) {
-        Preconditions.checkNotNull(metadataURI, "Metadata URI must not be null.");
-        List<Metric> metrics = new ArrayList<>();
-
-        metadataMetrics.forEach((metric, metricValue) -> {
-
-            // Create metric uri
-            StringBuilder metricUri = new StringBuilder(metadataURI.toString());
-            metricUri.append("/metrics/");
-            metricUri.append(DigestUtils.md5Hex(metric));
-
-            addMetric(metrics, metricUri.toString(), metric, metricValue);
-        });
-        return metrics;
-    }
-
-    /**
-     * We are using this method to reduce the NPath complexity measure. This method add a FM to the
-     * list if the metric valueUri URI is provided.
-     *
-     * @param metrics  List<Mertic> object
-     * @param uri      Metric uri
-     * @param typeUri  Metric typeUri uri
-     * @param valueUri Metric valueUri uri
-     */
-    private void addMetric(List<Metric> metrics, String uri, String typeUri, String valueUri) {
-        try {
-            Preconditions.checkNotNull(uri, "Metadata URI must not be null.");
-            Preconditions.checkState(!uri.isEmpty(), "Metadata URI must not be empty.");
-            Preconditions.checkNotNull(typeUri, "Type URI must not be null.");
-            Preconditions.checkState(!typeUri.isEmpty(), "Type URI must not be empty.");
-            Preconditions.checkNotNull(valueUri, "Value URI must not be null.");
-            Preconditions.checkState(!valueUri.isEmpty(), "Value URI must not be empty.");
-
-            Metric m = new Metric();
-            m.setUri(VALUEFACTORY.createIRI(uri));
-            m.setMetricType(VALUEFACTORY.createIRI(typeUri));
-            m.setValue(VALUEFACTORY.createIRI(valueUri));
-            metrics.add(m);
-        } catch (Exception e) {
-            LOGGER.error("Error adding metrics {}", e.getMessage());
-        }
-    }
-}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiGenerator.java b/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a9d81562f39edd5cbc97da7b311188069dfe694
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiGenerator.java
@@ -0,0 +1,492 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.openapi;
+
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.Content;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import io.swagger.v3.oas.models.tags.Tag;
+import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
+import nl.dtls.fairdatapoint.api.dto.member.MemberCreateDTO;
+import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+
+public class OpenApiGenerator {
+
+    public static final String FDP_TAG_PRIORITY = "FDP-TP";
+
+    public static final String TAG_PREFIX = "Metadata: ";
+
+    private static final Schema<String> SCHEMA_STRING = new Schema<String>().type("string");
+
+    private static final Schema<String> SCHEMA_ERROR = new Schema<ErrorDTO>().$ref("#/components/schemas/ErrorDTO");
+
+    private static final Schema<String> SCHEMA_META = new Schema<MetaDTO>().$ref("#/components/schemas/MetaDTO");
+
+    private static final Schema<String> SCHEMA_META_STATE_CHANGE = new Schema<MetaStateChangeDTO>().$ref("#/components/schemas/MetaStateChangeDTO");
+
+    private static final Schema<String> SCHEMA_MEMBER = new Schema<MemberDTO>().$ref("#/components/schemas/MemberDTO");
+
+    private static final ArraySchema SCHEMA_MEMBERS = new ArraySchema().items(SCHEMA_MEMBER);
+
+    private static final Schema<String> SCHEMA_MEMBER_CREATE = new Schema<MemberCreateDTO>().$ref("#/components/schemas/MemberDTO");
+
+    private static final Content CONTENT_RDF = new Content()
+            .addMediaType("text/turtle", new MediaType().schema(SCHEMA_STRING))
+            .addMediaType("application/ld+json", new MediaType().schema(SCHEMA_STRING))
+            .addMediaType("application/rdf+xml", new MediaType().schema(SCHEMA_STRING))
+            .addMediaType("text/n3", new MediaType().schema(SCHEMA_STRING));
+
+    private static final Content CONTENT_ERROR = new Content()
+            .addMediaType("text/plain", new MediaType().schema(SCHEMA_STRING))
+            .addMediaType("application/json", new MediaType().schema(SCHEMA_ERROR));
+
+    private static final Content CONTENT_META_STATE_CHANGE = new Content()
+            .addMediaType("application/json", new MediaType().schema(SCHEMA_META_STATE_CHANGE));
+
+    private static final ApiResponse RESPONSE_BAD_REQUEST = new ApiResponse()
+            .description("Bad Request")
+            .content(CONTENT_ERROR);
+
+    private static final ApiResponse RESPONSE_FORBIDDEN = new ApiResponse()
+            .description("Forbidden")
+            .content(CONTENT_ERROR);
+
+    private static final ApiResponse RESPONSE_UNAUTHORIZED = new ApiResponse()
+            .description("Unauthorized")
+            .content(CONTENT_ERROR);
+
+    private static final ApiResponse RESPONSE_NOT_FOUND = new ApiResponse()
+            .description("Not Found")
+            .content(CONTENT_ERROR);
+
+    private static final ApiResponse RESPONSE_NO_CONTENT = new ApiResponse()
+            .description("No Content");
+
+    private static final ApiResponse RESPONSE_INTERNAL_SERVER_ERROR = new ApiResponse()
+            .description("Internal Server Error")
+            .content(CONTENT_ERROR);
+
+    private static final ApiResponse RESPONSE_OK_RDF = new ApiResponse()
+            .description("OK")
+            .content(CONTENT_RDF);
+
+    private static final ApiResponses RESPONSES_RDF = new ApiResponses()
+            .addApiResponse("200", RESPONSE_OK_RDF)
+            .addApiResponse("400", RESPONSE_BAD_REQUEST)
+            .addApiResponse("401", RESPONSE_UNAUTHORIZED)
+            .addApiResponse("403", RESPONSE_FORBIDDEN)
+            .addApiResponse("404", RESPONSE_NOT_FOUND)
+            .addApiResponse("500", RESPONSE_INTERNAL_SERVER_ERROR);
+
+    private static final ApiResponses RESPONSES_DELETE = new ApiResponses()
+            .addApiResponse("204", RESPONSE_NO_CONTENT)
+            .addApiResponse("400", RESPONSE_BAD_REQUEST)
+            .addApiResponse("401", RESPONSE_UNAUTHORIZED)
+            .addApiResponse("403", RESPONSE_FORBIDDEN)
+            .addApiResponse("404", RESPONSE_NOT_FOUND)
+            .addApiResponse("500", RESPONSE_INTERNAL_SERVER_ERROR);
+
+    private static final ApiResponses RESPONSES_META = new ApiResponses()
+            .addApiResponse("200", new ApiResponse()
+                    .description("OK")
+                    .content(new Content()
+                            .addMediaType("application/json", new MediaType().schema(SCHEMA_META)))
+            )
+            .addApiResponse("400", RESPONSE_BAD_REQUEST)
+            .addApiResponse("401", RESPONSE_UNAUTHORIZED)
+            .addApiResponse("403", RESPONSE_FORBIDDEN)
+            .addApiResponse("404", RESPONSE_NOT_FOUND)
+            .addApiResponse("500", RESPONSE_INTERNAL_SERVER_ERROR);
+
+    private static final ApiResponses RESPONSES_META_STATE = new ApiResponses()
+            .addApiResponse("200", new ApiResponse()
+                    .description("OK")
+                    .content(CONTENT_META_STATE_CHANGE)
+            )
+            .addApiResponse("400", RESPONSE_BAD_REQUEST)
+            .addApiResponse("401", RESPONSE_UNAUTHORIZED)
+            .addApiResponse("403", RESPONSE_FORBIDDEN)
+            .addApiResponse("404", RESPONSE_NOT_FOUND)
+            .addApiResponse("500", RESPONSE_INTERNAL_SERVER_ERROR);
+
+    private static final ApiResponses RESPONSES_MEMBERS = new ApiResponses()
+            .addApiResponse("200", new ApiResponse()
+                    .description("OK")
+                    .content(new Content()
+                            .addMediaType("application/json", new MediaType().schema(SCHEMA_MEMBERS)))
+            )
+            .addApiResponse("400", RESPONSE_BAD_REQUEST)
+            .addApiResponse("401", RESPONSE_UNAUTHORIZED)
+            .addApiResponse("403", RESPONSE_FORBIDDEN)
+            .addApiResponse("404", RESPONSE_NOT_FOUND)
+            .addApiResponse("500", RESPONSE_INTERNAL_SERVER_ERROR);
+
+    private static final ApiResponses RESPONSES_MEMBER = new ApiResponses()
+            .addApiResponse("200", new ApiResponse()
+                    .description("OK")
+                    .content(new Content()
+                            .addMediaType("application/json", new MediaType().schema(SCHEMA_MEMBER)))
+            )
+            .addApiResponse("400", RESPONSE_BAD_REQUEST)
+            .addApiResponse("401", RESPONSE_UNAUTHORIZED)
+            .addApiResponse("403", RESPONSE_FORBIDDEN)
+            .addApiResponse("404", RESPONSE_NOT_FOUND)
+            .addApiResponse("500", RESPONSE_INTERNAL_SERVER_ERROR);
+
+    private static final RequestBody BODY_META_STATE = new RequestBody()
+            .description("New state")
+            .content(CONTENT_META_STATE_CHANGE)
+            .required(true);
+
+    private static final RequestBody BODY_MEMBERSHIP = new RequestBody()
+            .description("New membership")
+            .content(new Content().addMediaType("application/json", new MediaType().schema(SCHEMA_MEMBER_CREATE)))
+            .required(true);
+
+    private static final Parameter PARAM_USER_UUID = new Parameter()
+            .name("userUuid")
+            .in("path")
+            .schema(SCHEMA_STRING);
+
+    private static final Parameter PARAM_CHILD_PREFIX = new Parameter()
+            .name("childPrefix")
+            .in("path")
+            .schema(SCHEMA_STRING);
+
+    public static void generatePathsForRootResourceDefinition(Paths paths, ResourceDefinition resourceDefinition) {
+        String tag = TAG_PREFIX + resourceDefinition.getName();
+        String operationSuffix = resourceDefinition.getName();
+        Map<String, Object> extensions = Map.of("fdpResourceDefinition", resourceDefinition.getUuid());
+        // CRUD: GET, PUT, DELETE
+        paths.addPathItem(
+                "/",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix)
+                                .description("Get " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+                        .put(new Operation()
+                                .operationId("put" + operationSuffix)
+                                .description("Edit existing " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .requestBody(new RequestBody()
+                                        .description(resourceDefinition.getName() + " in RDF")
+                                        .content(CONTENT_RDF)
+                                        .required(true)
+                                )
+                                .responses(RESPONSES_RDF)
+                        )
+                        .delete(new Operation()
+                                .operationId("delete" + operationSuffix)
+                                .description("Delete existing " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_DELETE)
+                        )
+        );
+        // Spec (SHACL Shape)
+        paths.addPathItem(
+                "/spec",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Spec")
+                                .description("Get SHACL shape specification for " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // Expanded
+        paths.addPathItem(
+                "/expanded",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + StringUtils.capitalize(operationSuffix) + "Expanded")
+                                .description("Get " + resourceDefinition.getName() + " with its children")
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // Page
+        paths.addPathItem(
+                "/page/{childPrefix}",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "ChildrenPage")
+                                .description("Get a page of " + resourceDefinition.getName() + " children")
+                                .addParametersItem(PARAM_CHILD_PREFIX)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // Meta
+        paths.addPathItem(
+                "/meta",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Meta")
+                                .description("Get metadata (memberships and state) for " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_META)
+                        )
+        );
+        paths.addPathItem(
+                "/meta/state",
+                new PathItem()
+                        .extensions(extensions)
+                        .put(new Operation()
+                                .operationId("put" + operationSuffix + "MetaState")
+                                .description("Change state of " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .requestBody(BODY_META_STATE)
+                                .responses(RESPONSES_META_STATE)
+                        )
+        );
+        // Membership
+        paths.addPathItem(
+                "/members",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Members")
+                                .description("Get members of a specific " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_MEMBERS)
+                        )
+        );
+        paths.addPathItem(
+                "/members/{userUuid}",
+                new PathItem()
+                        .extensions(extensions)
+                        .put(new Operation()
+                                .operationId("put" + operationSuffix + "Member")
+                                .description("Set membership for specific user in some " + resourceDefinition.getName())
+                                .addParametersItem(PARAM_USER_UUID)
+                                .addTagsItem(tag)
+                                .requestBody(BODY_MEMBERSHIP)
+                                .responses(RESPONSES_MEMBER)
+                        )
+                        .delete(new Operation()
+                                .operationId("delete" + operationSuffix + "Member")
+                                .description("Set membership for specific user in some " + resourceDefinition.getName())
+                                .addParametersItem(PARAM_USER_UUID)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_DELETE)
+                        )
+        );
+    }
+
+    public static void generatePathsForResourceDefinition(Paths paths, ResourceDefinition resourceDefinition) {
+        String prefix = resourceDefinition.getUrlPrefix();
+        String operationSuffix = StringUtils.capitalize(prefix);
+        if (prefix.isEmpty()) {
+            generatePathsForRootResourceDefinition(paths, resourceDefinition);
+            return;
+        }
+        String parameterName = "uuid";
+        String nestedPrefix = "/" + prefix;
+        Parameter parameter = new Parameter()
+                .name(parameterName)
+                .in("path")
+                .schema(new Schema<String>().type("string"));
+        String tag = TAG_PREFIX + resourceDefinition.getName();
+        Map<String, Object> extensions = Map.of("fdpResourceDefinition", resourceDefinition.getUuid());
+        // CRUD: POST
+        paths.addPathItem(
+                "/" + prefix,
+                new PathItem()
+                        .extensions(extensions)
+                        .post(new Operation()
+                                .operationId("post" + operationSuffix)
+                                .description("Create a new " + resourceDefinition.getName())
+                                .addTagsItem(tag)
+                                .requestBody(new RequestBody()
+                                        .description(resourceDefinition.getName() + " in RDF")
+                                        .content(CONTENT_RDF)
+                                        .required(true)
+                                )
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // CRUD: GET, PUT, DELETE
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix)
+                                .description("Get " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+                        .put(new Operation()
+                                .operationId("put" + operationSuffix)
+                                .description("Edit existing " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .requestBody(new RequestBody()
+                                        .description(resourceDefinition.getName() + " in RDF")
+                                        .content(CONTENT_RDF)
+                                        .required(true)
+                                )
+                                .responses(RESPONSES_RDF)
+                        )
+                        .delete(new Operation()
+                                .operationId("delete" + operationSuffix)
+                                .description("Delete existing " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_DELETE)
+                        )
+        );
+        // Spec (SHACL Shape)
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/spec",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Spec")
+                                .description("Get SHACL shape specification for " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // Expanded
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/expanded",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Expanded")
+                                .description("Get " + resourceDefinition.getName() + " with its children")
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // Page
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/page/{childPrefix}",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "ChildrenPage")
+                                .description("Get a page of " + resourceDefinition.getName() + " children")
+                                .addParametersItem(parameter)
+                                .addParametersItem(PARAM_CHILD_PREFIX)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_RDF)
+                        )
+        );
+        // Meta
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/meta",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Meta")
+                                .description("Get metadata (memberships and state) for " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_META)
+                        )
+        );
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/meta/state",
+                new PathItem()
+                        .extensions(extensions)
+                        .put(new Operation()
+                                .operationId("put" + operationSuffix + "MetaState")
+                                .description("Change state of " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .requestBody(BODY_META_STATE)
+                                .responses(RESPONSES_META_STATE)
+                        )
+        );
+        // Membership
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/members",
+                new PathItem()
+                        .extensions(extensions)
+                        .get(new Operation()
+                                .operationId("get" + operationSuffix + "Members")
+                                .description("Get members of a specific " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_MEMBERS)
+                        )
+        );
+        paths.addPathItem(
+                nestedPrefix + "/{" + parameterName + "}/members/{userUuid}",
+                new PathItem()
+                        .extensions(extensions)
+                        .put(new Operation()
+                                .operationId("put" + operationSuffix + "Member")
+                                .description("Set membership for specific user in some " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addParametersItem(PARAM_USER_UUID)
+                                .addTagsItem(tag)
+                                .requestBody(BODY_MEMBERSHIP)
+                                .responses(RESPONSES_MEMBER)
+                        )
+                        .delete(new Operation()
+                                .operationId("delete" + operationSuffix + "Member")
+                                .description("Set membership for specific user in some " + resourceDefinition.getName())
+                                .addParametersItem(parameter)
+                                .addParametersItem(PARAM_USER_UUID)
+                                .addTagsItem(tag)
+                                .responses(RESPONSES_DELETE)
+                        )
+        );
+    }
+
+    public static Tag generateTag(ResourceDefinition resourceDefinition) {
+        return new Tag()
+                .name(TAG_PREFIX + resourceDefinition.getName())
+                .description("Metadata according to the " + resourceDefinition.getName() + " resource definition")
+                .extensions(Map.of(FDP_TAG_PRIORITY, 10));
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiService.java b/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiService.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8455ce6e73d00facf9253a3b2f7832979bba9a4
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiService.java
@@ -0,0 +1,118 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.openapi;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.extern.log4j.Log4j2;
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Service
+@Log4j2
+public class OpenApiService {
+
+    @Autowired
+    private OpenAPI openAPI;
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    private Paths getGenericPaths() {
+        return (Paths) openAPI.getExtensions().get("fdpGenericPaths");
+    }
+
+    private boolean isRelatedToResourceDefinition(PathItem pathItem, ResourceDefinition rd) {
+        String rdUuid = (String) pathItem.getExtensions().getOrDefault("fdpResourceDefinition", "");
+        return rdUuid.equals(rd.getUuid());
+    }
+
+    public void updateTags(List<ResourceDefinition> resourceDefinitions) {
+        openAPI.setTags(OpenApiTagsUtils.listTags(resourceDefinitions));
+    }
+
+    public void removeGenericPaths(ResourceDefinition rd) {
+        Paths fdpGenericPaths = getGenericPaths();
+        Set<String> toRemove = fdpGenericPaths
+                .entrySet()
+                .stream()
+                .filter(item -> isRelatedToResourceDefinition(item.getValue(), rd))
+                .map(Map.Entry::getKey)
+                .collect(Collectors.toSet());
+        log.info("Removing OpenAPI paths: {}", toRemove);
+        openAPI.getPaths().keySet().removeAll(toRemove);
+        fdpGenericPaths.keySet().removeAll(toRemove);
+        // Update tags
+        updateTags(resourceDefinitionRepository.findAll());
+    }
+
+    public void removeAllGenericPaths() {
+        Paths fdpGenericPaths = getGenericPaths();
+        log.info("Removing OpenAPI paths: {}", fdpGenericPaths.keySet());
+        openAPI.getPaths().keySet().removeAll(fdpGenericPaths.keySet());
+        fdpGenericPaths.clear();
+    }
+
+    public void updateGenericPaths(ResourceDefinition rd) {
+        Paths fdpGenericPaths = getGenericPaths();
+        // Cleanup
+        removeGenericPaths(rd);
+        // Generate
+        OpenApiGenerator.generatePathsForResourceDefinition(fdpGenericPaths, rd);
+        // Apply
+        log.info("Adding OpenAPI paths: {}", fdpGenericPaths.keySet());
+        openAPI.getPaths().putAll(fdpGenericPaths);
+        // Update tags
+        updateTags(resourceDefinitionRepository.findAll());
+    }
+
+    public void updateAllGenericPaths() {
+        Paths fdpGenericPaths = getGenericPaths();
+        // Cleanup
+        removeAllGenericPaths();
+        // Re-generate from Resource Definitions
+        List<ResourceDefinition> resourceDefinitions = resourceDefinitionRepository.findAll();
+        resourceDefinitions.forEach(rd -> OpenApiGenerator.generatePathsForResourceDefinition(fdpGenericPaths, rd));
+        // Apply
+        log.info("Adding OpenAPI paths: {}", fdpGenericPaths.keySet());
+        openAPI.getPaths().putAll(fdpGenericPaths);
+        updateTags(resourceDefinitions);
+    }
+
+    @PostConstruct
+    public void init() {
+        log.info("Initializing OpenAPI with generic paths");
+        updateAllGenericPaths();
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiTagsUtils.java b/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiTagsUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9c6f21d91e0535cefc5e97e283552ad5f802b05
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/openapi/OpenApiTagsUtils.java
@@ -0,0 +1,84 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.openapi;
+
+import io.swagger.v3.oas.models.tags.Tag;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class OpenApiTagsUtils {
+
+    private static final Comparator<String> STRING_COMPARATOR = Comparator.comparing(String::toString);
+
+    private static final Comparator<Tag> TAG_COMPARATOR = (o1, o2) -> {
+        int priority1 = (int)o1.getExtensions().getOrDefault(OpenApiGenerator.FDP_TAG_PRIORITY, 90);
+        int priority2 = (int)o2.getExtensions().getOrDefault(OpenApiGenerator.FDP_TAG_PRIORITY, 90);
+        if (priority1 < priority2) {
+            return -1;
+        } else if (priority1 > priority2) {
+            return 1;
+        }
+        return STRING_COMPARATOR.compare(o1.getName(), o2.getName());
+    };
+
+    public static final Tag METADATA_TAG = new Tag()
+            .name("Metadata")
+            .description("Common operations with all metadata")
+            .extensions(Map.of(OpenApiGenerator.FDP_TAG_PRIORITY, 0));
+
+    public static final Tag METADATA_MMODEL_TAG = new Tag()
+            .name("Metadata Model")
+            .description("Manipulation with model of metadata")
+            .extensions(Map.of(OpenApiGenerator.FDP_TAG_PRIORITY, 20));
+
+    public static final Tag METADATA_CLIENT_TAG = new Tag()
+            .name("Client")
+            .description("Endpoints for FAIR Data Point Client")
+            .extensions(Map.of(OpenApiGenerator.FDP_TAG_PRIORITY, 30));
+
+    public static final Tag METADATA_INDEX_TAG = new Tag()
+            .name("Index")
+            .description("FAIR Data Point Index endpoints")
+            .extensions(Map.of(OpenApiGenerator.FDP_TAG_PRIORITY, 40));
+
+    public static final Tag METADATA_AA_TAG = new Tag()
+            .name("Authentication and Authorization")
+            .description("Management of access to FDP (not specific type of metadata)")
+            .extensions(Map.of(OpenApiGenerator.FDP_TAG_PRIORITY, 50));
+
+    public static final Tag METADATA_USERMGMT_TAG = new Tag()
+            .name("User Management")
+            .description("Management of user accounts")
+            .extensions(Map.of(OpenApiGenerator.FDP_TAG_PRIORITY, 60));
+
+    public static final List<Tag> STATIC_TAGS = Arrays.asList(METADATA_TAG, METADATA_MMODEL_TAG, METADATA_CLIENT_TAG, METADATA_INDEX_TAG, METADATA_AA_TAG, METADATA_USERMGMT_TAG);
+
+    public static List<Tag> listTags(List<ResourceDefinition> resourceDefinitions) {
+        List<Tag> tags = resourceDefinitions.stream().map(OpenApiGenerator::generateTag).collect(Collectors.toList());
+        tags.addAll(STATIC_TAGS);
+        tags.sort(TAG_COMPARATOR);
+        return tags;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/ping/PingService.java b/src/main/java/nl/dtls/fairdatapoint/service/ping/PingService.java
new file mode 100644
index 0000000000000000000000000000000000000000..64d5e6aed13051d0ee3c6cf79e27d91f627b2026
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/ping/PingService.java
@@ -0,0 +1,80 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.ping;
+
+
+import lombok.extern.log4j.Log4j2;
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
+import nl.dtls.fairdatapoint.config.properties.PingProperties;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.service.settings.SettingsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+
+@Log4j2
+@Service
+@ConditionalOnProperty(name = "ping.enabled", havingValue = "true", matchIfMissing = true)
+public class PingService {
+
+    @Autowired
+    private PingProperties pingProperties;
+
+    @Autowired
+    private InstanceProperties instanceProperties;
+
+    @Autowired
+    private SettingsService settingsService;
+
+    @Autowired
+    private RestTemplate client;
+
+    @Scheduled(initialDelayString = "${ping.initDelay:#{10*1000}}", fixedRateString = "${ping.interval:P7D}")
+    public void ping() {
+        Settings settings = settingsService.getOrDefaults();
+        if (!settings.getPing().isEnabled() || !pingProperties.isEnabled()) {
+            return;
+        }
+        var request = Map.of("clientUrl", instanceProperties.getClientUrl());
+        for (String endpoint : settings.getPing().getEndpoints()) {
+            pingEndpoint(endpoint.trim(), request);
+        }
+    }
+
+    @Async
+    void pingEndpoint(String endpoint, Map<String, String> request) {
+        try {
+            log.info("Pinging {}", endpoint);
+            client.postForEntity(endpoint, request, String.class);
+        } catch (Exception e) {
+            log.warn("Failed to ping {}: {}", endpoint, e.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/profile/ProfileService.java b/src/main/java/nl/dtls/fairdatapoint/service/profile/ProfileService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d0f757a5558efa96a0d78a5d4a8b3e0227d1d11
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/profile/ProfileService.java
@@ -0,0 +1,93 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.profile;
+
+import nl.dtls.fairdatapoint.database.mongo.repository.ShapeRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionService;
+import nl.dtls.fairdatapoint.service.shape.ShapeService;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.impl.LinkedHashModel;
+import org.eclipse.rdf4j.model.util.ModelBuilder;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.*;
+
+@Service
+public class ProfileService {
+
+    private static final String PROFILE_PREFIX = "http://www.w3.org/ns/dx/prof/";
+
+    @Autowired
+    @Qualifier("persistentUrl")
+    private String persistentUrl;
+
+    @Autowired
+    private ShapeRepository shapeRepository;
+
+    @Autowired
+    private ResourceDefinitionService resourceDefinitionService;
+
+    private Model getProfileForResourceDefinition(ResourceDefinition rd, IRI uri) {
+        Model profile = new LinkedHashModel();
+        profile.add(uri, RDF.TYPE, i(format("%sProfile", PROFILE_PREFIX)));
+        profile.add(uri, RDFS.LABEL, l(format("%s Profile", rd.getName())));
+        profile.add(uri, i(format("%sisProfileOf", PROFILE_PREFIX)), i(format("%s/profile/core",
+                persistentUrl)));
+        rd.getShapeUuids().forEach(shapeUuid -> shapeRepository.findByUuid(shapeUuid).map(shape -> {
+            ModelBuilder modelBuilder = new ModelBuilder();
+            Resource resource = bn();
+            modelBuilder.subject(resource);
+            modelBuilder.add(RDF.TYPE, i(format("%s#ResourceDescriptor", PROFILE_PREFIX)));
+            modelBuilder.add(DCTERMS.FORMAT, i("https://w3id.org/mediatype/text/turtle"));
+            modelBuilder.add(DCTERMS.CONFORMS_TO, i("https://www.w3.org/TR/shacl/"));
+            modelBuilder.add(i(format("%shasRole", PROFILE_PREFIX)), i(format("%srole/Validation",
+                    PROFILE_PREFIX)));
+            modelBuilder.add(i(format("%shasArtifact", PROFILE_PREFIX)), i(format("%s/shapes/%s",
+                    persistentUrl, shapeUuid)));
+            profile.add(uri, i(format("%shasResource", PROFILE_PREFIX)), resource);
+            profile.addAll(new ArrayList<>(modelBuilder.build()));
+            return null;
+        }));
+        return profile;
+    }
+
+    public Optional<Model> getProfileByUuid(String uuid, IRI uri) {
+        return resourceDefinitionService.getByUuid(uuid).map(rd -> getProfileForResourceDefinition(rd, uri));
+    }
+
+    public IRI getProfileUri(ResourceDefinition rd) {
+        return i(format("%s/profile/%s", persistentUrl, rd.getUuid()));
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/reset/FactoryDefaults.java b/src/main/java/nl/dtls/fairdatapoint/service/reset/FactoryDefaults.java
new file mode 100644
index 0000000000000000000000000000000000000000..d387e5b2291f84a8cc03481d9c2e1b20c77cf358
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/reset/FactoryDefaults.java
@@ -0,0 +1,395 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.reset;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import nl.dtls.fairdatapoint.entity.membership.Membership;
+import nl.dtls.fairdatapoint.entity.membership.MembershipPermission;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import nl.dtls.fairdatapoint.entity.resource.*;
+import nl.dtls.fairdatapoint.entity.shape.Shape;
+import nl.dtls.fairdatapoint.entity.shape.ShapeType;
+import nl.dtls.fairdatapoint.entity.user.User;
+import nl.dtls.fairdatapoint.entity.user.UserRole;
+import nl.dtls.fairdatapoint.service.shape.ShapeShaclUtils;
+import nl.dtls.fairdatapoint.vocabulary.DATACITE;
+import nl.dtls.fairdatapoint.vocabulary.FDP;
+import nl.dtls.fairdatapoint.vocabulary.R3D;
+import nl.dtls.fairdatapoint.vocabulary.Sio;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.bson.BasicBSONObject;
+import org.bson.Document;
+import org.bson.types.BasicBSONList;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Statement;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.FOAF;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.*;
+
+public class FactoryDefaults {
+
+    //== USERS
+    // Changes: Migration_0001_Init
+    public static final User USER_ALBERT = User.builder()
+            .uuid("7e64818d-6276-46fb-8bb1-732e6e09f7e9")
+            .firstName("Albert")
+            .lastName("Einstein")
+            .email("albert.einstein@example.com")
+            .passwordHash("$2a$10$t2foZfp7cZFQo2u/33ZqTu2WNitBqYd2EY2tQO0/rBUdf8QfsAxyW")
+            .role(UserRole.ADMIN)
+            .build();
+
+    public static final User USER_NIKOLA = User.builder()
+            .uuid("b5b92c69-5ed9-4054-954d-0121c29b6800")
+            .firstName("Nikola")
+            .lastName("Tesla")
+            .email("nikola.tesla@example.com")
+            .passwordHash("$2a$10$t2foZfp7cZFQo2u/33ZqTu2WNitBqYd2EY2tQO0/rBUdf8QfsAxyW")
+            .role(UserRole.USER)
+            .build();
+
+    //== MEMBERSHIPS
+    // Changes: Migration_0001_Init, Migration_0004_ResourceDefinition
+    public static final Membership MEMBERSHIP_OWNER = Membership.builder()
+            .uuid("49f2bcfd-ef0a-4a3a-a1a3-0fc72a6892a8")
+            .name("Owner")
+            .permissions(List.of(
+                    new MembershipPermission(2, 'W'),
+                    new MembershipPermission(4, 'C'),
+                    new MembershipPermission(8, 'D'),
+                    new MembershipPermission(16, 'A')
+            ))
+            .allowedEntities(List.of(
+                    "a0949e72-4466-4d53-8900-9436d1049a4b",
+                    "2f08228e-1789-40f8-84cd-28e3288c3604",
+                    "02c649de-c579-43bb-b470-306abdc808c7"
+            ))
+            .build();
+
+    public static final Membership MEMBERSHIP_DATA_PROVIDER = Membership.builder()
+            .uuid("87a2d984-7db2-43f6-805c-6b0040afead5")
+            .name("Data Provider")
+            .permissions(List.of(
+                    new MembershipPermission(4, 'C')
+            ))
+            .allowedEntities(List.of(
+                    "a0949e72-4466-4d53-8900-9436d1049a4b"
+            ))
+            .build();
+
+    //== SHAPE UUIDS
+    private static final String SHAPE_RESOURCE_UUID = "6a668323-3936-4b53-8380-a4fd2ed082ee";
+
+    private static final String SHAPE_REPOSITORY_UUID = "a92958ab-a414-47e6-8e17-68ba96ba3a2b";
+
+    private static final String SHAPE_CATALOG_UUID = "2aa7ba63-d27a-4c0e-bfa6-3a4e250f4660";
+
+    private static final String SHAPE_DATASET_UUID = "866d7fb8-5982-4215-9c7c-18d0ed1bd5f3";
+
+    private static final String SHAPE_DISTRIBUTION_UUID = "ebacbf83-cd4f-4113-8738-d73c0735b0ab";
+
+    //== RESOURCE DEFINITIONS
+    // Changes: Migration_0002_CustomMetamodel, Migration_0004_ResourceDefinition
+    public static final ResourceDefinition RESOURCE_DEFINITION_REPOSITORY = ResourceDefinition.builder()
+            .uuid("77aaad6a-0136-4c6e-88b9-07ffccd0ee4c")
+            .name("Repository")
+            .urlPrefix("")
+            .shapeUuids(List.of(
+                    SHAPE_RESOURCE_UUID,
+                    SHAPE_REPOSITORY_UUID
+            ))
+            .children(List.of(
+                    ResourceDefinitionChild.builder()
+                            .resourceDefinitionUuid("a0949e72-4466-4d53-8900-9436d1049a4b")
+                            .relationUri("http://www.re3data.org/schema/3-0#dataCatalog")
+                            .listView(
+                                    ResourceDefinitionChildListView.builder()
+                                            .title("Catalogs")
+                                            .tagsUri("http://www.w3.org/ns/dcat#themeTaxonomy")
+                                            .metadata(List.of())
+                                            .build()
+                            )
+                            .build()
+            ))
+            .externalLinks(List.of())
+            .build();
+
+    public static final ResourceDefinition RESOURCE_DEFINITION_CATALOG = ResourceDefinition.builder()
+            .uuid("a0949e72-4466-4d53-8900-9436d1049a4b")
+            .name("Catalog")
+            .urlPrefix("catalog")
+            .shapeUuids(List.of(
+                    SHAPE_RESOURCE_UUID,
+                    SHAPE_CATALOG_UUID
+            ))
+            .children(List.of(
+                    ResourceDefinitionChild.builder()
+                            .resourceDefinitionUuid("2f08228e-1789-40f8-84cd-28e3288c3604")
+                            .relationUri("http://www.w3.org/ns/dcat#dataset")
+                            .listView(
+                                    ResourceDefinitionChildListView.builder()
+                                            .title("Datasets")
+                                            .tagsUri("http://www.w3.org/ns/dcat#theme")
+                                            .metadata(List.of())
+                                            .build()
+                            )
+                            .build()
+            ))
+            .externalLinks(List.of())
+            .build();
+
+    public static final ResourceDefinition RESOURCE_DEFINITION_DATASET = ResourceDefinition.builder()
+            .uuid("2f08228e-1789-40f8-84cd-28e3288c3604")
+            .name("Dataset")
+            .urlPrefix("dataset")
+            .shapeUuids(List.of(
+                    SHAPE_RESOURCE_UUID,
+                    SHAPE_DATASET_UUID
+            ))
+            .children(List.of(
+                    ResourceDefinitionChild.builder()
+                            .resourceDefinitionUuid("02c649de-c579-43bb-b470-306abdc808c7")
+                            .relationUri("http://www.w3.org/ns/dcat#distribution")
+                            .listView(
+                                    ResourceDefinitionChildListView.builder()
+                                            .title("Distributions")
+                                            .tagsUri(null)
+                                            .metadata(List.of(
+                                                    new ResourceDefinitionChildListViewMetadata(
+                                                            "Media Type",
+                                                            "http://www.w3.org/ns/dcat#mediaType"
+                                                    )
+                                            ))
+                                            .build()
+                            )
+                            .build()
+            ))
+            .externalLinks(List.of())
+            .build();
+
+    public static ResourceDefinition RESOURCE_DEFINITION_DISTRIBUTION = ResourceDefinition.builder()
+            .uuid("02c649de-c579-43bb-b470-306abdc808c7")
+            .name("Distribution")
+            .urlPrefix("distribution")
+            .shapeUuids(List.of(
+                    SHAPE_RESOURCE_UUID,
+                    SHAPE_DISTRIBUTION_UUID
+            ))
+            .children(List.of())
+            .externalLinks(List.of(
+                    new ResourceDefinitionLink(
+                            "Access online",
+                            "http://www.w3.org/ns/dcat#accessURL"
+                    ),
+                    new ResourceDefinitionLink(
+                            "Download",
+                            "http://www.w3.org/ns/dcat#downloadURL"
+                    )
+            ))
+            .build();
+
+    //== SHAPES
+    //== Changes: Migration_0003_ShapeDefinition, Migration_0005_UpdateShapeDefinition, Migration_0006_ShapesSharing
+    public static Shape shapeResource() throws Exception {
+        String definition = loadShape("shape-resource.ttl");
+        return Shape.builder()
+                .uuid(SHAPE_RESOURCE_UUID)
+                .name("Resource")
+                .type(ShapeType.INTERNAL)
+                .published(false)
+                .definition(definition)
+                .targetClasses(ShapeShaclUtils.extractTargetClasses(definition))
+                .build();
+    }
+
+    public static Shape shapeRepository() throws Exception {
+        String definition = loadShape("shape-repository.ttl");
+        return Shape.builder()
+                .uuid(SHAPE_REPOSITORY_UUID)
+                .name("Repository")
+                .type(ShapeType.INTERNAL)
+                .published(false)
+                .definition(definition)
+                .targetClasses(ShapeShaclUtils.extractTargetClasses(definition))
+                .build();
+    }
+
+    public static Shape shapeCatalog() throws Exception {
+        String definition = loadShape("shape-catalog.ttl");
+        return Shape.builder()
+                .uuid(SHAPE_CATALOG_UUID)
+                .name("Catalog")
+                .type(ShapeType.INTERNAL)
+                .published(false)
+                .definition(definition)
+                .targetClasses(ShapeShaclUtils.extractTargetClasses(definition))
+                .build();
+    }
+
+    public static Shape shapeDataset() throws Exception {
+        String definition = loadShape("shape-dataset.ttl");
+        return Shape.builder()
+                .uuid(SHAPE_DATASET_UUID)
+                .name("Dataset")
+                .type(ShapeType.CUSTOM)
+                .published(false)
+                .definition(definition)
+                .targetClasses(ShapeShaclUtils.extractTargetClasses(definition))
+                .build();
+    }
+
+    public static Shape shapeDistribution() throws Exception {
+        String definition = loadShape("shape-distribution.ttl");
+        return Shape.builder()
+                .uuid(SHAPE_DISTRIBUTION_UUID)
+                .name("Distribution")
+                .type(ShapeType.CUSTOM)
+                .published(false)
+                .definition(definition)
+                .targetClasses(ShapeShaclUtils.extractTargetClasses(definition))
+                .build();
+    }
+
+    // Repository ACL
+    public static Document aclRepository(String persistentUrl) {
+        BasicBSONObject owner = new BasicBSONObject()
+                .append("name", USER_ALBERT.getUuid())
+                .append("isPrincipal", true);
+        Document acl = new Document();
+        acl.append("className", "nl.dtls.fairdatapoint.entity.metadata.FDPMetadata");
+        acl.append("instanceId", persistentUrl);
+        acl.append("owner", owner);
+        acl.append("inheritPermissions", true);
+        BasicBSONList permissions = new BasicBSONList();
+        permissions.add(
+                new Document()
+                        .append("sid", owner)
+                        .append("permission", 2)
+                        .append("granting", true)
+                        .append("auditFailure", false)
+                        .append("auditSuccess", false));
+        permissions.add(
+                new Document()
+                        .append("sid", owner)
+                        .append("permission", 4)
+                        .append("granting", true)
+                        .append("auditFailure", false)
+                        .append("auditSuccess", false));
+        permissions.add(
+                new Document()
+                        .append("sid", owner)
+                        .append("permission", 8)
+                        .append("granting", true)
+                        .append("auditFailure", false)
+                        .append("auditSuccess", false));
+        permissions.add(
+                new Document()
+                        .append("sid", owner)
+                        .append("permission", 16)
+                        .append("granting", true)
+                        .append("auditFailure", false)
+                        .append("auditSuccess", false));
+        acl.append("permissions", permissions);
+        acl.append("_class", "org.springframework.security.acls.domain.MongoAcl");
+        return acl;
+    }
+
+    // Repository RDF statements
+    private static final String LIPSUM_TEXT = "Duis pellentesque, nunc a fringilla varius, magna dui porta quam, nec " +
+            "ultricies augue turpis sed velit. Donec id consectetur ligula. Suspendisse pharetra egestas " +
+            "massa, vel varius leo viverra at. Donec scelerisque id ipsum id semper. Maecenas facilisis augue" +
+            " vel justo molestie aliquet. Maecenas sed mattis lacus, sed viverra risus. Donec iaculis quis " +
+            "lacus vitae scelerisque. Nullam fermentum lectus nisi, id vulputate nisi congue nec. Morbi " +
+            "fermentum justo at justo bibendum, at tempus ipsum tempor. Donec facilisis nibh sed lectus " +
+            "blandit venenatis. Cras ullamcorper, justo vitae feugiat commodo, orci metus suscipit purus, " +
+            "quis sagittis turpis ante eget ex. Pellentesque malesuada a metus eu pulvinar. Morbi rutrum " +
+            "euismod eros at varius. Duis finibus dapibus ex, a hendrerit mauris efficitur at.";
+
+    public static List<Statement> repositoryStatements(String persistentUrl, IRI license, IRI language, String accessRightsDescription) {
+        List<Statement> s = new ArrayList<>();
+        IRI baseUrl = i(persistentUrl);
+        FactoryDefaults.add(s, RDF.TYPE, R3D.REPOSITORY, baseUrl);
+        FactoryDefaults.add(s, RDF.TYPE, i("http://www.w3.org/ns/dcat#Resource"), baseUrl);
+        FactoryDefaults.add(s, DCTERMS.TITLE, l("My FAIR Data Point"), baseUrl);
+        FactoryDefaults.add(s, RDFS.LABEL, l("My FAIR Data Point"), baseUrl);
+        FactoryDefaults.add(s, DCTERMS.HAS_VERSION, l(1.0f), baseUrl);
+        FactoryDefaults.add(s, FDP.METADATAISSUED, l(OffsetDateTime.now()), baseUrl);
+        FactoryDefaults.add(s, FDP.METADATAMODIFIED, l(OffsetDateTime.now()), baseUrl);
+        FactoryDefaults.add(s, DCTERMS.LICENSE, license, baseUrl);
+        FactoryDefaults.add(s, DCTERMS.DESCRIPTION, l(LIPSUM_TEXT), baseUrl);
+        FactoryDefaults.add(s, DCTERMS.CONFORMS_TO, i("https://www.purl.org/fairtools/fdp/schema/0.1/fdpMetadata"), baseUrl);
+        FactoryDefaults.add(s, DCTERMS.LANGUAGE, language, baseUrl);
+        // Identifier
+        IRI identifierIri = i(persistentUrl + "#identifier");
+        FactoryDefaults.add(s, FDP.METADATAIDENTIFIER, identifierIri, baseUrl);
+        FactoryDefaults.add(s, identifierIri, RDF.TYPE, DATACITE.IDENTIFIER, baseUrl);
+        FactoryDefaults.add(s, identifierIri, DCTERMS.IDENTIFIER, l(persistentUrl), baseUrl);
+        // Repository Identifier
+        FactoryDefaults.add(s, R3D.REPOSITORYIDENTIFIER, identifierIri, baseUrl);
+        // Access Rights
+        IRI arIri = i(persistentUrl + "#accessRights");
+        FactoryDefaults.add(s, DCTERMS.ACCESS_RIGHTS, arIri, baseUrl);
+        FactoryDefaults.add(s, arIri, RDF.TYPE, DCTERMS.RIGHTS_STATEMENT, baseUrl);
+        FactoryDefaults.add(s, arIri, DCTERMS.DESCRIPTION, l(accessRightsDescription), baseUrl);
+        // Publisher
+        IRI publisherIri = i(persistentUrl + "#publisher");
+        FactoryDefaults.add(s, DCTERMS.PUBLISHER, publisherIri, baseUrl);
+        FactoryDefaults.add(s, publisherIri, RDF.TYPE, FOAF.AGENT, baseUrl);
+        FactoryDefaults.add(s, publisherIri, FOAF.NAME, l("Default Publisher"), baseUrl);
+        return s;
+    }
+
+    public static Metadata metadataRepository(String persistentUrl) {
+        return Metadata.builder()
+                .uri(persistentUrl)
+                .state(MetadataState.PUBLISHED)
+                .build();
+    }
+
+    private static String loadShape(String name) throws Exception {
+        return Resources.toString(
+                FactoryDefaults.class.getResource(name),
+                Charsets.UTF_8
+        );
+    }
+
+    private static void add(List<Statement> statements, IRI predicate, org.eclipse.rdf4j.model.Value object, IRI base) {
+        statements.add(s(base, predicate, object, base));
+    }
+
+    private static void add(List<Statement> statements, IRI subject, IRI predicate, org.eclipse.rdf4j.model.Value object, IRI base) {
+        statements.add(s(subject, predicate, object, base));
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/reset/ResetService.java b/src/main/java/nl/dtls/fairdatapoint/service/reset/ResetService.java
new file mode 100644
index 0000000000000000000000000000000000000000..080d2acf8e9e62b3cca3202fccdf661cd2eba299
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/reset/ResetService.java
@@ -0,0 +1,242 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.reset;
+
+import com.mongodb.client.MongoCollection;
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.api.dto.reset.ResetDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.*;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
+import nl.dtls.fairdatapoint.service.metadata.generic.GenericMetadataService;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionTargetClassesCache;
+import nl.dtls.fairdatapoint.service.settings.SettingsService;
+import nl.dtls.fairdatapoint.vocabulary.DATACITE;
+import nl.dtls.fairdatapoint.vocabulary.FDP;
+import nl.dtls.fairdatapoint.vocabulary.R3D;
+import nl.dtls.fairdatapoint.vocabulary.Sio;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.bson.Document;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Statement;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.FOAF;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+import org.eclipse.rdf4j.repository.Repository;
+import org.eclipse.rdf4j.repository.RepositoryConnection;
+import org.eclipse.rdf4j.repository.RepositoryException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.acls.dao.AclRepository;
+import org.springframework.security.acls.model.AclCache;
+import org.springframework.stereotype.Service;
+
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.l;
+
+@Slf4j
+@Service
+public class ResetService {
+
+    @Autowired
+    @Qualifier("persistentUrl")
+    private String persistentUrl;
+
+    @Value("${metadataProperties.accessRightsDescription:This resource has no access restriction}")
+    private String accessRightsDescription;
+
+    @Autowired
+    private IRI license;
+
+    @Autowired
+    private IRI language;
+
+    @Autowired
+    protected Repository repository;
+
+    @Autowired
+    private ApiKeyRepository apiKeyRepository;
+
+    @Autowired
+    private MembershipRepository membershipRepository;
+
+    @Autowired
+    private AclRepository aclRepository;
+
+    @Autowired
+    private AclCache aclCache;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private ShapeRepository shapeRepository;
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    @Autowired
+    private ResourceDefinitionTargetClassesCache resourceDefinitionTargetClassesCache;
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    @Autowired
+    private GenericMetadataService genericMetadataService;
+
+    @Autowired
+    private SettingsService settingsService;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    @PreAuthorize("hasRole('ADMIN')")
+    public void resetToFactoryDefaults(ResetDTO reqDto) throws Exception {
+        log.info("Resetting to factory defaults");
+        if (reqDto.isSettings()) {
+            settingsService.resetSettings();
+        }
+        if (reqDto.isUsers() || reqDto.isMetadata()) {
+            clearMemberships();
+            restoreDefaultMemberships();
+        }
+        if (reqDto.isUsers()) {
+            clearApiKeys();
+            clearUsers();
+            restoreDefaultUsers();
+        }
+        if (reqDto.isMetadata()) {
+            clearMetadata();
+            restoreDefaultMetadata();
+        }
+        if (reqDto.isResourceDefinitions()) {
+            clearShapes();
+            clearResourceDefinitions();
+            restoreDefaultShapes();
+            restoreDefaultResourceDefinitions();
+        }
+        resourceDefinitionCache.computeCache();
+        resourceDefinitionTargetClassesCache.computeCache();
+    }
+
+    private void clearApiKeys() {
+        log.debug("Clearing API keys");
+        apiKeyRepository.deleteAll();
+    }
+
+    private void clearMemberships() {
+        log.debug("Clearing memberships");
+        membershipRepository.deleteAll();
+        log.debug("Clearing ACL cache");
+        aclRepository.deleteAll();
+        aclCache.clearCache();
+    }
+
+    private void clearUsers() {
+        log.debug("Clearing users");
+        userRepository.deleteAll();
+    }
+
+    private void clearShapes() {
+        log.debug("Clearing SHACL shapes");
+        shapeRepository.deleteAll();
+    }
+
+    private void clearResourceDefinitions() {
+        log.debug("Clearing resource definitions");
+        resourceDefinitionRepository.deleteAll();
+    }
+
+    private void clearMetadata() throws MetadataServiceException {
+        log.debug("Clearing metadata");
+        Optional<ResourceDefinition> rd = resourceDefinitionRepository.findByUrlPrefix("");
+        if (rd.isPresent()) {
+            genericMetadataService.delete(i(persistentUrl), rd.get());
+            metadataRepository.deleteAll();
+        }
+    }
+
+    private void restoreDefaultUsers() {
+        log.debug("Creating default users");
+        userRepository.save(FactoryDefaults.USER_ALBERT);
+        userRepository.save(FactoryDefaults.USER_NIKOLA);
+    }
+
+    private void restoreDefaultMemberships() {
+        log.debug("Creating default users");
+        membershipRepository.save(FactoryDefaults.MEMBERSHIP_OWNER);
+        membershipRepository.save(FactoryDefaults.MEMBERSHIP_DATA_PROVIDER);
+
+        MongoCollection<Document> aclCol = mongoTemplate.getCollection("ACL");
+        aclCol.insertOne(FactoryDefaults.aclRepository(persistentUrl));
+    }
+
+    private void restoreDefaultMetadata() {
+        log.debug("Creating default metadata");
+        try (RepositoryConnection conn = repository.getConnection()) {
+            List<Statement> s = FactoryDefaults.repositoryStatements(
+                    persistentUrl,
+                    license,
+                    language,
+                    accessRightsDescription
+            );
+            conn.add(s);
+            metadataRepository.save(FactoryDefaults.metadataRepository(persistentUrl));
+        } catch (RepositoryException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    private void restoreDefaultShapes() throws Exception {
+        log.debug("Creating default shapes");
+        shapeRepository.save(FactoryDefaults.shapeResource());
+        shapeRepository.save(FactoryDefaults.shapeRepository());
+        shapeRepository.save(FactoryDefaults.shapeCatalog());
+        shapeRepository.save(FactoryDefaults.shapeDataset());
+        shapeRepository.save(FactoryDefaults.shapeDistribution());
+    }
+
+    private void restoreDefaultResourceDefinitions() {
+        log.debug("Creating default resource definitions");
+        resourceDefinitionRepository.save(FactoryDefaults.RESOURCE_DEFINITION_REPOSITORY);
+        resourceDefinitionRepository.save(FactoryDefaults.RESOURCE_DEFINITION_CATALOG);
+        resourceDefinitionRepository.save(FactoryDefaults.RESOURCE_DEFINITION_DATASET);
+        resourceDefinitionRepository.save(FactoryDefaults.RESOURCE_DEFINITION_DISTRIBUTION);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionCache.java b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd2cbbd63df36ec0e685446c221cc1d4edc75fe9
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionCache.java
@@ -0,0 +1,108 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.resource;
+
+import lombok.Getter;
+import lombok.Setter;
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.Cache;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.validation.constraints.NotNull;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static nl.dtls.fairdatapoint.config.CacheConfig.RESOURCE_DEFINITION_CACHE;
+import static nl.dtls.fairdatapoint.config.CacheConfig.RESOURCE_DEFINITION_PARENT_CACHE;
+
+@Service
+public class ResourceDefinitionCache {
+
+    @Getter
+    private static class ResourceDefinitionParents {
+        private final HashSet<ResourceDefinition> parents = new HashSet<>();
+
+        public void add(ResourceDefinition rdParent) {
+            parents.add(rdParent);
+        }
+    }
+
+    @Autowired
+    private ConcurrentMapCacheManager cacheManager;
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @PostConstruct
+    public void computeCache() {
+        // Get cache
+        Cache cache = cache();
+        Cache parentCache = parentCache();
+
+        // Clear cache
+        cache.clear();
+        parentCache.clear();
+
+        // Add to cache
+        List<ResourceDefinition> rds = resourceDefinitionRepository.findAll();
+        rds.forEach(rd -> {
+            parentCache.put(rd.getUuid(), new ResourceDefinitionParents());
+        });
+        rds.forEach(rd -> {
+            cache.put(rd.getUuid(), rd);
+            rd.getChildren().forEach(c -> parentCache.get(c.getResourceDefinitionUuid(), ResourceDefinitionParents.class).add(rd));
+        });
+    }
+
+    public ResourceDefinition getByUuid(String uuid) {
+        return cache().get(uuid, ResourceDefinition.class);
+    }
+
+    public Set<ResourceDefinition> getParentsByUuid(String uuid) {
+        var parents = parentCache().get(uuid, ResourceDefinitionParents.class);
+        if (parents == null) {
+            computeCache(); // Try to recompute cache (the object should be there)
+            parents = parentCache().get(uuid, ResourceDefinitionParents.class);
+            if (parents == null) {
+                return Collections.emptySet();
+            }
+        }
+        return parents.getParents();
+    }
+
+    private Cache cache() {
+        return cacheManager.getCache(RESOURCE_DEFINITION_CACHE);
+    }
+
+    private Cache parentCache() {
+        return cacheManager.getCache(RESOURCE_DEFINITION_PARENT_CACHE);
+    }
+
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..04d464840c1e2bb59f439ccb65d0490d9e6a6a30
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionMapper.java
@@ -0,0 +1,65 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.resource;
+
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionChangeDTO;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionDTO;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class ResourceDefinitionMapper {
+
+    public ResourceDefinition fromChangeDTO(ResourceDefinitionChangeDTO dto, String uuid) {
+        return new ResourceDefinition(
+                uuid,
+                dto.getName(),
+                dto.getUrlPrefix(),
+                dto.getShapeUuids(),
+                dto.getChildren(),
+                dto.getExternalLinks());
+    }
+
+    public ResourceDefinitionChangeDTO toChangeDTO(ResourceDefinition rd) {
+        return new ResourceDefinitionChangeDTO(
+                rd.getName(),
+                rd.getUrlPrefix(),
+                rd.getShapeUuids(),
+                rd.getChildren(),
+                rd.getExternalLinks());
+    }
+
+    public ResourceDefinitionDTO toDTO(ResourceDefinition rd, List<String> targetClassUris) {
+        return new ResourceDefinitionDTO(
+                rd.getUuid(),
+                rd.getName(),
+                rd.getUrlPrefix(),
+                rd.getShapeUuids(),
+                targetClassUris,
+                rd.getChildren(),
+                rd.getExternalLinks()
+        );
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionService.java b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionService.java
index 4e4a57ebf3e4e5a96ea0c947421d30544f6d6631..ac34448246b13913f5831846beca2d9157ae9f74 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionService.java
@@ -22,39 +22,173 @@
  */
 package nl.dtls.fairdatapoint.service.resource;
 
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionChangeDTO;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionDTO;
 import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.membership.MembershipService;
+import nl.dtls.fairdatapoint.service.openapi.OpenApiService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Service;
+import org.springframework.validation.BindException;
 
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import static java.lang.String.format;
 
 @Service
 public class ResourceDefinitionService {
 
+    @Autowired
+    @Qualifier("persistentUrl")
+    private String persistentUrl;
+
     @Autowired
     private ResourceDefinitionRepository resourceDefinitionRepository;
 
-    public ResourceDefinition getByUuid(String uuid) {
-        Optional<ResourceDefinition> oRd = resourceDefinitionRepository.findByUuid(uuid);
+    @Autowired
+    private ResourceDefinitionValidator resourceDefinitionValidator;
+
+    @Autowired
+    private ResourceDefinitionMapper resourceDefinitionMapper;
+
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    @Autowired
+    private ResourceDefinitionTargetClassesCache targetClassesCache;
+
+    @Autowired
+    private MembershipService membershipService;
+
+    @Autowired
+    private OpenApiService openApiService;
+
+    public ResourceDefinitionDTO toDTO(ResourceDefinition rd) {
+        return resourceDefinitionMapper.toDTO(rd, getTargetClassUris(rd));
+    }
+
+    public List<ResourceDefinitionDTO> getAll() {
+        return resourceDefinitionRepository
+                .findAll()
+                .stream()
+                .map(this::toDTO)
+                .toList();
+    }
+
+    public Optional<ResourceDefinition> getByUuid(String uuid) {
+        return resourceDefinitionRepository.findByUuid(uuid);
+    }
+
+    public Optional<ResourceDefinitionDTO> getDTOByUuid(String uuid) {
+        return getByUuid(uuid).map(this::toDTO);
+    }
+
+    public ResourceDefinition getByUrl(String url) {
+        String[] parts = url.replace(persistentUrl, "").split("/");
+        String parentPrefix = ""; // Repository
+        if (parts.length > 1 && parts[0].isEmpty()) {
+            parentPrefix = parts[1]; // Other prefix (first empty caused by leading /)
+        } else if (parts.length > 0) {
+            parentPrefix = parts[0]; // Other prefix
+        }
+        return getByUrlPrefix(parentPrefix);
+    }
+
+    public ResourceDefinition getByUrlPrefix(String urlPrefix) {
+        Optional<ResourceDefinition> oRd = resourceDefinitionRepository.findByUrlPrefix(urlPrefix);
         if (oRd.isEmpty()) {
-            throw new IllegalStateException(
-                    format("Resource with provided uuid ('%s') is not defined", uuid)
+            throw new ResourceNotFoundException(
+                    format("Resource with provided uri prefix ('%s') is not defined", urlPrefix)
             );
         }
         return oRd.get();
     }
 
-    public ResourceDefinition getByUriPrefix(String uriPrefix) {
-        Optional<ResourceDefinition> oRd = resourceDefinitionRepository.findByUriPrefix(uriPrefix);
+    @PreAuthorize("hasRole('ADMIN')")
+    public ResourceDefinitionDTO create(ResourceDefinitionChangeDTO reqDto) throws BindException {
+        String uuid = UUID.randomUUID().toString();
+        ResourceDefinition rd = resourceDefinitionMapper.fromChangeDTO(reqDto, uuid);
+
+        // TODO: check if shapes exist
+
+        resourceDefinitionValidator.validate(rd);
+        resourceDefinitionRepository.save(rd);
+        resourceDefinitionCache.computeCache();
+        targetClassesCache.computeCache();
+
+        membershipService.addToMembership(rd);
+        openApiService.updateGenericPaths(rd);
+        return toDTO(rd);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    public Optional<ResourceDefinitionDTO> update(String uuid, ResourceDefinitionChangeDTO reqDto) throws BindException {
+        Optional<ResourceDefinition> oRd = resourceDefinitionRepository.findByUuid(uuid);
         if (oRd.isEmpty()) {
-            throw new IllegalStateException(
-                    format("Resource with provided uri prefix ('%s') is not defined", uriPrefix)
-            );
+            return Optional.empty();
         }
-        return oRd.get();
+        ResourceDefinition rd = oRd.get();
+        ResourceDefinition updatedRd = resourceDefinitionMapper.fromChangeDTO(reqDto, rd.getUuid());
+        updatedRd.setId(rd.getId());
+
+        // TODO: check if shapes exist
+
+        resourceDefinitionValidator.validate(updatedRd);
+        resourceDefinitionRepository.save(updatedRd);
+        resourceDefinitionCache.computeCache();
+        targetClassesCache.computeCache();
+        openApiService.updateGenericPaths(updatedRd);
+        return Optional.of(updatedRd).map(this::toDTO);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    public boolean deleteByUuid(String uuid) {
+        // 1. Get resource definition
+        Optional<ResourceDefinition> oRd = resourceDefinitionRepository.findByUuid(uuid);
+        if (oRd.isEmpty()) {
+            return false;
+        }
+        ResourceDefinition rd = oRd.get();
+
+        // 2. Delete from parent resource definitions
+        Set<ResourceDefinition> rdParents = resourceDefinitionCache.getParentsByUuid(rd.getUuid());
+        rdParents.forEach(rdParent -> {
+            rdParent = resourceDefinitionRepository.findByUuid(rdParent.getUuid()).get();
+            rdParent.setChildren(
+                    rdParent.getChildren()
+                            .stream()
+                            .filter(x -> !x.getResourceDefinitionUuid().equals(rd.getUuid()))
+                            .collect(Collectors.toList())
+            );
+            resourceDefinitionRepository.save(rdParent);
+        });
+
+        // 3. Delete resource definition
+        resourceDefinitionRepository.delete(rd);
+
+        // 4. Delete entity from membership
+        membershipService.removeFromMembership(rd);
+
+        // 5. Recompute cache
+        resourceDefinitionCache.computeCache();
+        targetClassesCache.computeCache();
+
+        // 6. Delete from OpenAPI docs
+        openApiService.removeGenericPaths(rd);
+        return true;
     }
 
+    public List<String> getTargetClassUris(ResourceDefinition rd) {
+        List<String> result = targetClassesCache.getByUuid(rd.getUuid());
+        if (result == null) {
+            targetClassesCache.computeCache();
+            return targetClassesCache.getByUuid(rd.getUuid());
+        }
+        return result;
+    }
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionTargetClassesCache.java b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionTargetClassesCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b22f4c23354d33178909b4a6f1767a45dc5be67
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionTargetClassesCache.java
@@ -0,0 +1,83 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.resource;
+
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.database.mongo.repository.ShapeRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.shape.Shape;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.Cache;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static nl.dtls.fairdatapoint.config.CacheConfig.RESOURCE_DEFINITION_TARGET_CLASSES_CACHE;
+
+@Service
+public class ResourceDefinitionTargetClassesCache {
+
+    @Autowired
+    private ConcurrentMapCacheManager cacheManager;
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @Autowired
+    private ShapeRepository shapeRepository;
+
+    @PostConstruct
+    public void computeCache() {
+        // Get cache
+        Cache cache = cache();
+
+        // Clear cache
+        cache.clear();
+
+        // Add to cache
+        List<ResourceDefinition> rds = resourceDefinitionRepository.findAll();
+        Map<String, Shape> shapes = shapeRepository.findAll().stream().collect(Collectors.toMap(Shape::getUuid, Function.identity()));
+        rds.forEach(rd -> {
+            Set<String> targetClassUris = new HashSet<>();
+            rd.getShapeUuids().forEach(shapeUuid -> {
+                if (shapes.containsKey(shapeUuid)) {
+                    targetClassUris.addAll(shapes.get(shapeUuid).getTargetClasses());
+                }
+            });
+            cache.put(rd.getUuid(), targetClassUris.stream().toList());
+        });
+    }
+
+    public List<String> getByUuid(String uuid) {
+        return cache().get(uuid, List.class);
+    }
+
+    private Cache cache() {
+        return cacheManager.getCache(RESOURCE_DEFINITION_TARGET_CLASSES_CACHE);
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionValidator.java b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..a15c1153b371fcba9892bd3b9c34c74c19b09f21
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionValidator.java
@@ -0,0 +1,85 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.resource;
+
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.BindException;
+
+import java.util.List;
+import java.util.Optional;
+
+import static nl.dtls.fairdatapoint.util.ValidationUtil.uniquenessValidationFailed;
+
+@Service
+public class ResourceDefinitionValidator {
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    public void validate(ResourceDefinition reqDto) throws BindException {
+        // Check uniqueness
+        Optional<ResourceDefinition> oRdByName = resourceDefinitionRepository.findByName(reqDto.getName());
+        if (oRdByName.isPresent() && !oRdByName.get().getUuid().equals(reqDto.getUuid())) {
+            uniquenessValidationFailed("name", reqDto);
+        }
+        Optional<ResourceDefinition> oRdByUrlPrefix =
+                resourceDefinitionRepository.findByUrlPrefix(reqDto.getUrlPrefix());
+        if (oRdByUrlPrefix.isPresent() && !oRdByUrlPrefix.get().getUuid().equals(reqDto.getUuid())) {
+            uniquenessValidationFailed("urlPrefix", reqDto);
+        }
+
+        // Check existence of connected entities
+        for (ResourceDefinitionChild child : reqDto.getChildren()) {
+            if (resourceDefinitionCache.getByUuid(child.getResourceDefinitionUuid()) == null) {
+                throw new ValidationException("Child doesn't exist");
+            }
+        }
+
+        // Check existence of dependency cycles
+        validateDependencyCycles(reqDto, reqDto.getChildren());
+    }
+
+    private void validateDependencyCycles(ResourceDefinition reqDto, List<ResourceDefinitionChild> children) {
+        for (ResourceDefinitionChild child : children) {
+            String childUuid = child.getResourceDefinitionUuid();
+            if (reqDto.getUuid().equals(childUuid)) {
+                throw new ValidationException("Detect dependency cycle through child");
+            }
+
+            ResourceDefinition rdChild = resourceDefinitionCache.getByUuid(childUuid);
+            if (rdChild.getChildren().isEmpty()) {
+                return;
+            }
+            validateDependencyCycles(reqDto, rdChild.getChildren());
+        }
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/search/SearchService.java b/src/main/java/nl/dtls/fairdatapoint/service/search/SearchService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1960a00280e1f3ca83ae92f0f3305c55d1d11545
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/search/SearchService.java
@@ -0,0 +1,84 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.search;
+
+import nl.dtls.fairdatapoint.api.dto.search.SearchQueryDTO;
+import nl.dtls.fairdatapoint.api.dto.search.SearchResultDTO;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepository;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import nl.dtls.fairdatapoint.entity.search.SearchResult;
+import nl.dtls.fairdatapoint.service.metadata.state.MetadataStateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toList;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.l;
+
+@Service
+public class SearchService {
+
+    @Autowired
+    private GenericMetadataRepository metadataRepository;
+
+    @Autowired
+    private MetadataStateService metadataStateService;
+
+    public List<SearchResultDTO> search(SearchQueryDTO reqDto) throws MetadataRepositoryException {
+        return metadataRepository.findByLiteral(l(reqDto.getQ()))
+                .stream()
+                .collect(Collectors.groupingBy(SearchResult::getUri, Collectors.mapping(i -> i, toList())))
+                .entrySet()
+                .stream()
+                .filter(entry -> {
+                    try {
+                        return !metadataStateService.get(i(entry.getKey())).getState().equals(MetadataState.DRAFT);
+                    } catch (ResourceNotFoundException e) {
+                        return true;
+                    }
+                })
+                .map(entry -> new SearchResultDTO(
+                        entry.getKey(),
+                        entry.getValue()
+                                .stream()
+                                .map(SearchResult::getType)
+                                .distinct()
+                                .filter(t -> !t.equals("http://www.w3.org/ns/dcat#Resource"))
+                                .collect(Collectors.toList()),
+                        entry.getValue().get(0).getTitle(),
+                        entry.getValue().get(0).getDescription(),
+                        entry.getValue()
+                                .stream()
+                                .map(SearchResult::getRelation)
+                                .distinct()
+                                .collect(Collectors.toList())
+                ))
+                .collect(toList());
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/security/MongoUserDetailsService.java b/src/main/java/nl/dtls/fairdatapoint/service/security/MongoUserDetailsService.java
index a95737cc6d52a697b295a5721592a8cef2acf959..b41478b0f75a90627b9622f0ad151051dede6f26 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/security/MongoUserDetailsService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/security/MongoUserDetailsService.java
@@ -23,12 +23,12 @@
 package nl.dtls.fairdatapoint.service.security;
 
 import nl.dtls.fairdatapoint.database.mongo.repository.UserRepository;
+import nl.dtls.fairdatapoint.entity.exception.UnauthorizedException;
 import nl.dtls.fairdatapoint.entity.user.User;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -43,10 +43,10 @@ public class MongoUserDetailsService implements UserDetailsService {
     private UserRepository repository;
 
     @Override
-    public UserDetails loadUserByUsername(String uuid) throws UsernameNotFoundException {
+    public UserDetails loadUserByUsername(String uuid) {
         Optional<User> oUser = repository.findByUuid(uuid);
         if (oUser.isEmpty()) {
-            throw new UsernameNotFoundException("User not found");
+            throw new UnauthorizedException("User not found");
         }
         User user = oUser.get();
         List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(format("ROLE_%s",
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsCache.java b/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..93b7f1f8c229b13a2325a7b47cff8d4eb7881ce2
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsCache.java
@@ -0,0 +1,71 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.settings;
+
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.Cache;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.List;
+
+import static nl.dtls.fairdatapoint.config.CacheConfig.SETTINGS_CACHE;
+
+@Service
+public class SettingsCache {
+
+    @Autowired
+    private ConcurrentMapCacheManager cacheManager;
+
+    @Autowired
+    private SettingsRepository settingsRepository;
+
+    private static final String SETTINGS_KEY = "settings";
+
+    @PostConstruct
+    public void updateCachedSettings() {
+        updateCachedSettings(settingsRepository.findFirstBy().orElse(Settings.getDefault()));
+    }
+
+    public void updateCachedSettings(Settings settings) {
+        // Get cache
+        Cache cache = cache();
+
+        // Clear cache
+        cache.clear();
+
+        // Add to cache
+        cache.put(SETTINGS_KEY, settings);
+    }
+
+    public Settings getOrDefaults() {
+        return cache().get(SETTINGS_KEY, Settings.class);
+    }
+
+    private Cache cache() {
+        return cacheManager.getCache(SETTINGS_CACHE);
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..784860fac5535c9dd4ca087d866119e172d6811f
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsMapper.java
@@ -0,0 +1,104 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.settings;
+
+import nl.dtls.fairdatapoint.api.dto.settings.*;
+import nl.dtls.fairdatapoint.config.properties.InstanceProperties;
+import nl.dtls.fairdatapoint.config.properties.PingProperties;
+import nl.dtls.fairdatapoint.config.properties.RepositoryProperties;
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.entity.settings.SettingsPing;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SettingsMapper {
+
+    @Autowired
+    private InstanceProperties instanceProperties;
+
+    @Autowired
+    private PingProperties pingProperties;
+
+    @Autowired
+    private RepositoryProperties repositoryProperties;
+
+    public SettingsDTO toDTO(Settings settings) {
+        return new SettingsDTO(
+                instanceProperties.getClientUrl(),
+                instanceProperties.getPersistentUrl(),
+                settings.getMetadataMetrics(),
+                toDTO(settings.getPing()),
+                getRepositoryDTO()
+        );
+    }
+
+    public SettingsPingDTO toDTO(SettingsPing settingsPing) {
+        return new SettingsPingDTO(
+                settingsPing.isEnabled(),
+                settingsPing.getEndpoints(),
+                pingProperties.getInterval().toString()
+        );
+    }
+
+    public SettingsRepositoryDTO getRepositoryDTO() {
+        return new SettingsRepositoryDTO(
+                repositoryProperties.getStringType(),
+                repositoryProperties.getDir(),
+                repositoryProperties.getUrl(),
+                repositoryProperties.getRepository(),
+                repositoryProperties.getUsername(),
+                repositoryProperties.getPassword() != null ? "<SECRET>" : null
+        );
+    }
+
+    public Settings fromUpdateDTO(SettingsUpdateDTO dto, Settings settings) {
+        return settings.toBuilder()
+                .metadataMetrics(dto.getMetadataMetrics())
+                .ping(fromUpdateDTO(dto.getPing(), settings.getPing()))
+                .build();
+    }
+
+    public SettingsPing fromUpdateDTO(SettingsPingUpdateDTO dto, SettingsPing settingsPing) {
+        return settingsPing.toBuilder()
+                .enabled(dto.isEnabled())
+                .endpoints(dto.getEndpoints())
+                .build();
+    }
+
+    public SettingsUpdateDTO toUpdateDTO(Settings settings) {
+        return new SettingsUpdateDTO(
+                settings.getMetadataMetrics(),
+                toUpdateDTO(settings.getPing())
+        );
+    }
+
+    public SettingsPingUpdateDTO toUpdateDTO(SettingsPing settingsPing) {
+        return new SettingsPingUpdateDTO(
+                settingsPing.isEnabled(),
+                settingsPing.getEndpoints()
+        );
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsService.java b/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsService.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc50a60c22f89ce40f9dbfa99f014265ef92018e
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/settings/SettingsService.java
@@ -0,0 +1,62 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.settings;
+
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsUpdateDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SettingsService {
+
+    @Autowired
+    private SettingsRepository repository;
+
+    @Autowired
+    private SettingsMapper mapper;
+
+    @Autowired
+    private SettingsCache cache;
+
+    public Settings getOrDefaults() {
+        return cache.getOrDefaults();
+    }
+
+    public SettingsDTO getCurrentSettings() {
+        return mapper.toDTO(getOrDefaults());
+    }
+
+    public SettingsDTO updateSettings(SettingsUpdateDTO dto) {
+        Settings settings = repository.save(mapper.fromUpdateDTO(dto, getOrDefaults()));
+        cache.updateCachedSettings(settings);
+        return mapper.toDTO(settings);
+    }
+
+    public SettingsDTO resetSettings() {
+        return updateSettings(mapper.toUpdateDTO(Settings.getDefault()));
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeMapper.java
index c85723efcc9e96da4bbf26d7d52b0a3b5c2d3119..7e068de5e3e83e2aac029b8eb78ee087c489ac78 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeMapper.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeMapper.java
@@ -24,6 +24,7 @@ package nl.dtls.fairdatapoint.service.shape;
 
 import nl.dtls.fairdatapoint.api.dto.shape.ShapeChangeDTO;
 import nl.dtls.fairdatapoint.api.dto.shape.ShapeDTO;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeRemoteDTO;
 import nl.dtls.fairdatapoint.entity.shape.Shape;
 import nl.dtls.fairdatapoint.entity.shape.ShapeType;
 import org.springframework.stereotype.Service;
@@ -36,8 +37,11 @@ public class ShapeMapper {
                 new ShapeDTO(
                         shape.getUuid(),
                         shape.getName(),
+                        shape.isPublished(),
                         shape.getType(),
-                        shape.getDefinition());
+                        shape.getDefinition(),
+                        shape.getTargetClasses().stream().sorted().toList()
+                );
     }
 
     public Shape fromChangeDTO(ShapeChangeDTO dto, String uuid) {
@@ -46,8 +50,11 @@ public class ShapeMapper {
                         null,
                         uuid,
                         dto.getName(),
+                        dto.isPublished(),
                         ShapeType.CUSTOM,
-                        dto.getDefinition());
+                        dto.getDefinition(),
+                        ShapeShaclUtils.extractTargetClasses(dto.getDefinition())
+                );
 
     }
 
@@ -56,7 +63,28 @@ public class ShapeMapper {
                 shape
                         .toBuilder()
                         .name(dto.getName())
+                        .published(dto.isPublished())
                         .definition(dto.getDefinition())
+                        .targetClasses(ShapeShaclUtils.extractTargetClasses(dto.getDefinition()))
                         .build();
     }
+
+    public ShapeRemoteDTO toRemoteDTO(String fdpUrl, ShapeDTO shape) {
+        return
+                new ShapeRemoteDTO(
+                        fdpUrl,
+                        shape.getUuid(),
+                        shape.getName(),
+                        shape.getDefinition()
+                );
+    }
+
+    public ShapeChangeDTO fromRemoteDTO(ShapeRemoteDTO shape) {
+        return
+                new ShapeChangeDTO(
+                        shape.getName(),
+                        false,
+                        shape.getDefinition()
+                );
+    }
 }
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeRetrievalUtils.java b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeRetrievalUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..fffe2a27bb5178284bf18984e50a7a7a7fb6d056
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeRetrievalUtils.java
@@ -0,0 +1,70 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.shape;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeDTO;
+import nl.dtls.fairdatapoint.entity.exception.ShapeImportException;
+import org.springframework.http.HttpHeaders;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+
+public class ShapeRetrievalUtils {
+
+    private static final HttpClient client = HttpClient.newBuilder()
+            .version(HttpClient.Version.HTTP_2)
+            .followRedirects(HttpClient.Redirect.ALWAYS)
+            .connectTimeout(Duration.ofMinutes(1))
+            .build();
+
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    private static final TypeReference<List<ShapeDTO>> responseType = new TypeReference<>() {
+    };
+
+    public static List<ShapeDTO> retrievePublishedShapes(String fdpUrl) {
+        try {
+            HttpRequest request = HttpRequest.newBuilder()
+                    .uri(URI.create(fdpUrl.replaceAll("/$", "") + "/shapes/public"))
+                    .header(HttpHeaders.ACCEPT, "application/json")
+                    .GET().build();
+            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+            return objectMapper.readValue(response.body(), responseType);
+        } catch (JsonProcessingException e) {
+            throw new ShapeImportException(fdpUrl, "Cannot process response: " + e.getMessage());
+        } catch (IOException e) {
+            throw new ShapeImportException(fdpUrl, "Cannot get response: " + e.getMessage());
+        } catch (Exception e) {
+            throw new ShapeImportException(fdpUrl, e.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeService.java b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeService.java
index 3c500a2916c912bfa4421650fe975c2b34f77ed0..2d0d23534fbed5103874993c52bc62cba331ba0a 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeService.java
@@ -24,10 +24,15 @@ package nl.dtls.fairdatapoint.service.shape;
 
 import nl.dtls.fairdatapoint.api.dto.shape.ShapeChangeDTO;
 import nl.dtls.fairdatapoint.api.dto.shape.ShapeDTO;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeRemoteDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
 import nl.dtls.fairdatapoint.database.mongo.repository.ShapeRepository;
+import nl.dtls.fairdatapoint.entity.exception.ShapeImportException;
 import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import nl.dtls.fairdatapoint.entity.shape.Shape;
 import nl.dtls.fairdatapoint.entity.shape.ShapeType;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionTargetClassesCache;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.model.impl.LinkedHashModel;
@@ -39,7 +44,9 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
+import static java.lang.String.format;
 import static java.util.Optional.empty;
 import static java.util.Optional.of;
 import static java.util.stream.Collectors.toList;
@@ -50,12 +57,18 @@ public class ShapeService {
     @Autowired
     private ShapeRepository shapeRepository;
 
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
     @Autowired
     private ShapeMapper shapeMapper;
 
     @Autowired
     private ShapeValidator shapeValidator;
 
+    @Autowired
+    private ResourceDefinitionTargetClassesCache targetClassesCache;
+
     public List<ShapeDTO> getShapes() {
         List<Shape> shapes = shapeRepository.findAll();
         return
@@ -65,6 +78,15 @@ public class ShapeService {
                         .collect(toList());
     }
 
+    public List<ShapeDTO> getPublishedShapes() {
+        List<Shape> shapes = shapeRepository.findAllByPublishedIsTrue();
+        return
+                shapes
+                        .stream()
+                        .map(shapeMapper::toDTO)
+                        .collect(toList());
+    }
+
     public Optional<ShapeDTO> getShapeByUuid(String uuid) {
         return
                 shapeRepository
@@ -72,12 +94,20 @@ public class ShapeService {
                         .map(shapeMapper::toDTO);
     }
 
+    public Optional<Model> getShapeContentByUuid(String uuid) {
+        return
+                shapeRepository
+                        .findByUuid(uuid)
+                        .map(shape -> RdfIOUtil.read(shape.getDefinition(), ""));
+    }
+
     @PreAuthorize("hasRole('ADMIN')")
     public ShapeDTO createShape(ShapeChangeDTO reqDto) {
         shapeValidator.validate(reqDto);
         String uuid = UUID.randomUUID().toString();
         Shape shape = shapeMapper.fromChangeDTO(reqDto, uuid);
         shapeRepository.save(shape);
+        targetClassesCache.computeCache();
         return shapeMapper.toDTO(shape);
     }
 
@@ -89,12 +119,9 @@ public class ShapeService {
             return empty();
         }
         Shape shape = oShape.get();
-        if (shape.getType() == ShapeType.INTERNAL) {
-            throw new ValidationException("You can't edit INTERNAL Shape");
-        }
-
         Shape updatedShape = shapeMapper.fromChangeDTO(reqDto, shape);
         shapeRepository.save(updatedShape);
+        targetClassesCache.computeCache();
         return of(shapeMapper.toDTO(updatedShape));
     }
 
@@ -105,7 +132,17 @@ public class ShapeService {
             return false;
         }
         Shape shape = oShape.get();
+
+        List<ResourceDefinition> resourceDefinitions = resourceDefinitionRepository.findByShapeUuidsIsContaining(shape.getUuid());
+        if (!resourceDefinitions.isEmpty()) {
+            throw new ValidationException(format("Shape is used in %d resource definitions", resourceDefinitions.size()));
+        }
+
+        if (shape.getType() == ShapeType.INTERNAL) {
+            throw new ValidationException("You can't delete INTERNAL Shape");
+        }
         shapeRepository.delete(shape);
+        targetClassesCache.computeCache();
         return true;
     }
 
@@ -118,4 +155,30 @@ public class ShapeService {
         return shacl;
     }
 
-}
\ No newline at end of file
+    public List<ShapeRemoteDTO> getRemoteShapes(String fdpUrl) {
+        List<ShapeDTO> shapes = ShapeRetrievalUtils.retrievePublishedShapes(fdpUrl);
+        return shapes
+                .stream()
+                .map(s -> shapeMapper.toRemoteDTO(fdpUrl, s))
+                .collect(Collectors.toList());
+    }
+
+    private ShapeDTO importShape(ShapeChangeDTO reqDto) {
+        shapeValidator.validate(reqDto);
+        String uuid = UUID.randomUUID().toString();
+        Shape shape = shapeMapper.fromChangeDTO(reqDto, uuid);
+        shapeRepository.save(shape);
+        return shapeMapper.toDTO(shape);
+    }
+
+    public List<ShapeDTO> importShapes(List<ShapeRemoteDTO> reqDtos) {
+        List<ShapeDTO> result =
+                reqDtos
+                        .stream()
+                        .map(s -> shapeMapper.fromRemoteDTO(s))
+                        .map(this::importShape)
+                        .collect(Collectors.toList());
+        targetClassesCache.computeCache();
+        return result;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeShaclUtils.java b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeShaclUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a6f1da22df7698968aefd6bb4ac574fbf534de1
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/shape/ShapeShaclUtils.java
@@ -0,0 +1,59 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.shape;
+
+import nl.dtls.fairdatapoint.util.RdfIOUtil;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.vocabulary.SHACL;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+
+public class ShapeShaclUtils {
+
+    public static Set<String> extractTargetClasses(String definition) {
+        var model = RdfIOUtil.read(definition, "");
+        return model
+                .filter(null, SHACL.TARGET_CLASS, null)
+                .objects()
+                .stream()
+                .map(Value::stringValue)
+                .filter(iri -> isRootNodeOfTargetClass(model, iri))
+                .collect(Collectors.toSet());
+    }
+
+    private static boolean isRootNodeOfTargetClass(Model model, String iri) {
+        var resource = i(iri);
+        for (Resource subject : model.filter(null, null, resource).subjects()) {
+            if (model.contains(null, null, subject)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/user/UserMapper.java b/src/main/java/nl/dtls/fairdatapoint/service/user/UserMapper.java
index 829f131bf15d972776f26e27cc725e900de62dfe..94107ce2f4bf750e327aa9645d993bc37d35167d 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/user/UserMapper.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/user/UserMapper.java
@@ -75,6 +75,16 @@ public class UserMapper {
                         .build();
     }
 
+    public User fromProfileChangeDTO(UserProfileChangeDTO dto, User user) {
+        return
+                user
+                        .toBuilder()
+                        .firstName(dto.getFirstName())
+                        .lastName(dto.getLastName())
+                        .email(dto.getEmail())
+                        .build();
+    }
+
     public User fromPasswordDTO(UserPasswordDTO reqDto, User user) {
         return
                 user
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/user/UserService.java b/src/main/java/nl/dtls/fairdatapoint/service/user/UserService.java
index 322a1f355d3a2396bfcc83492ce86ae6f8cd6457..97e09d9b0a55d926e1e6298a06517762cab12a17 100755
--- a/src/main/java/nl/dtls/fairdatapoint/service/user/UserService.java
+++ b/src/main/java/nl/dtls/fairdatapoint/service/user/UserService.java
@@ -22,12 +22,8 @@
  */
 package nl.dtls.fairdatapoint.service.user;
 
-import nl.dtls.fairdatapoint.api.dto.user.UserChangeDTO;
-import nl.dtls.fairdatapoint.api.dto.user.UserCreateDTO;
-import nl.dtls.fairdatapoint.api.dto.user.UserDTO;
-import nl.dtls.fairdatapoint.api.dto.user.UserPasswordDTO;
+import nl.dtls.fairdatapoint.api.dto.user.*;
 import nl.dtls.fairdatapoint.database.mongo.repository.UserRepository;
-import nl.dtls.fairdatapoint.entity.exception.ValidationException;
 import nl.dtls.fairdatapoint.entity.user.User;
 import nl.dtls.fairdatapoint.service.member.MemberService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,7 +35,6 @@ import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
 
-import static java.lang.String.format;
 import static java.util.Optional.empty;
 import static java.util.Optional.of;
 import static java.util.stream.Collectors.toList;
@@ -53,6 +48,9 @@ public class UserService {
     @Autowired
     private UserMapper userMapper;
 
+    @Autowired
+    private UserValidator userValidator;
+
     @Autowired
     private MemberService memberService;
 
@@ -87,10 +85,7 @@ public class UserService {
 
     @PreAuthorize("hasRole('ADMIN')")
     public UserDTO createUser(UserCreateDTO reqDto) {
-        Optional<User> oUser = userRepository.findByEmail(reqDto.getEmail());
-        if (oUser.isPresent()) {
-            throw new ValidationException(format("Email '%s' is already taken", reqDto.getEmail()));
-        }
+        userValidator.validateEmail(null, reqDto.getEmail());
         String uuid = UUID.randomUUID().toString();
         User user = userMapper.fromCreateDTO(reqDto, uuid);
         userRepository.save(user);
@@ -99,20 +94,29 @@ public class UserService {
 
     @PreAuthorize("hasRole('ADMIN')")
     public Optional<UserDTO> updateUser(String uuid, UserChangeDTO reqDto) {
-        Optional<User> oUserEmail = userRepository.findByEmail(reqDto.getEmail());
-        if (oUserEmail.isPresent() && !uuid.equals(oUserEmail.get().getUuid())) {
-            throw new ValidationException(format("Email '%s' is already taken", reqDto.getEmail()));
-        }
         Optional<User> oUser = userRepository.findByUuid(uuid);
         if (oUser.isEmpty()) {
             return empty();
         }
         User user = oUser.get();
+        userValidator.validateEmail(uuid, reqDto.getEmail());
         User updatedUser = userMapper.fromChangeDTO(reqDto, user);
         userRepository.save(updatedUser);
         return of(userMapper.toDTO(updatedUser));
     }
 
+    public Optional<UserDTO> updateCurrentUser(UserProfileChangeDTO reqDto) {
+        Optional<User> oUser = getCurrentUserUuid().flatMap(uuid -> userRepository.findByUuid(uuid));
+        if (oUser.isEmpty()) {
+            return empty();
+        }
+        User user = oUser.get();
+        userValidator.validateEmail(user.getUuid(), reqDto.getEmail());
+        User updatedUser = userMapper.fromProfileChangeDTO(reqDto, user);
+        userRepository.save(updatedUser);
+        return of(userMapper.toDTO(updatedUser));
+    }
+
     @PreAuthorize("hasRole('ADMIN')")
     public Optional<UserDTO> updatePassword(String uuid, UserPasswordDTO reqDto) {
         Optional<User> oUser = userRepository.findByUuid(uuid);
@@ -125,6 +129,17 @@ public class UserService {
         return of(userMapper.toDTO(updatedUser));
     }
 
+    public Optional<UserDTO> updatePasswordForCurrentUser(UserPasswordDTO reqDto) {
+        Optional<User> oUser = getCurrentUserUuid().flatMap(uuid -> userRepository.findByUuid(uuid));
+        if (oUser.isEmpty()) {
+            return empty();
+        }
+        User user = oUser.get();
+        User updatedUser = userMapper.fromPasswordDTO(reqDto, user);
+        userRepository.save(updatedUser);
+        return of(userMapper.toDTO(updatedUser));
+    }
+
     @PreAuthorize("hasRole('ADMIN')")
     public boolean deleteUser(String uuid) {
         Optional<User> oUser = userRepository.findByUuid(uuid);
diff --git a/src/main/java/nl/dtls/fairdatapoint/service/user/UserValidator.java b/src/main/java/nl/dtls/fairdatapoint/service/user/UserValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..f904d38bf6f87292f7bca88e07c68155c6086422
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/service/user/UserValidator.java
@@ -0,0 +1,48 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.user;
+
+import nl.dtls.fairdatapoint.database.mongo.repository.UserRepository;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.user.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+import static java.lang.String.format;
+
+@Service
+public class UserValidator {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    public void validateEmail(String uuid, String email) {
+        Optional<User> oUserEmail = userRepository.findByEmail(email);
+        if (oUserEmail.isPresent() && !oUserEmail.get().getUuid().equals(uuid)) {
+            throw new ValidationException(format("Email '%s' is already taken", email));
+        }
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/util/HttpUtil.java b/src/main/java/nl/dtls/fairdatapoint/util/HttpUtil.java
index bf0c8ca0b7264edf8089f5c541e8b7317dc27780..03c81ed80a84461f80683cddd21db5eda5c0fff3 100755
--- a/src/main/java/nl/dtls/fairdatapoint/util/HttpUtil.java
+++ b/src/main/java/nl/dtls/fairdatapoint/util/HttpUtil.java
@@ -26,27 +26,57 @@ import lombok.extern.slf4j.Slf4j;
 import nl.dtls.fairdatapoint.entity.exception.ValidationException;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.rio.RDFFormat;
+import org.springframework.http.HttpHeaders;
 
 import javax.servlet.http.HttpServletRequest;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.UUID;
 
+import static java.util.Optional.of;
+import static java.util.Optional.ofNullable;
 import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
 
 @Slf4j
 public class HttpUtil {
 
+    private static final String[] IP_HEADER_CANDIDATES = {
+            "X-Forwarded-For",
+            "X-Real-IP",
+            "Proxy-Client-IP",
+            "WL-Proxy-Client-IP",
+            "HTTP_X_FORWARDED_FOR",
+            "HTTP_X_FORWARDED",
+            "HTTP_X_CLUSTER_CLIENT_IP",
+            "HTTP_CLIENT_IP",
+            "HTTP_FORWARDED_FOR",
+            "HTTP_FORWARDED",
+            "HTTP_VIA",
+            "REMOTE_ADDR"
+    };
+
+    public static String getClientIpAddress(HttpServletRequest request, Boolean behindProxy) {
+        if (behindProxy) {
+            for (String header : IP_HEADER_CANDIDATES) {
+                String ipList = request.getHeader(header);
+                if (ipList != null && ipList.length() != 0 && !"unknown".equalsIgnoreCase(ipList)) {
+                    return ipList.split(",")[0];
+                }
+            }
+        }
+        return request.getRemoteAddr();
+    }
+
     public static String getRequestURL(HttpServletRequest request, String persistentUrl) {
         String urlS = request.getRequestURL().toString();
-        log.info("Original requesed url {}", urlS);
+        log.info("Original requested url {}", urlS);
         try {
             urlS = removeLastSlash(urlS.replace("/expanded", ""));
             persistentUrl = removeLastSlash(persistentUrl);
 
             URL url = new URL(urlS);
             String modifiedUrl = persistentUrl + url.getPath();
-            log.info("Modified requesed url {}", modifiedUrl);
+            log.info("Modified requested url {}", modifiedUrl);
 
             return modifiedUrl;
 
@@ -65,20 +95,21 @@ public class HttpUtil {
         if (name == null) {
             return RDFFormat.TURTLE;
         }
-        switch (name) {
-            case "text/plain":
-                return RDFFormat.TURTLE;
-            case "text/turtle":
-                return RDFFormat.TURTLE;
-            case "application/ld+json":
-                return RDFFormat.JSONLD;
-            case "application/rdf+xml":
-                return RDFFormat.RDFXML;
-            case "text/n3":
-                return RDFFormat.N3;
-            default:
-                return RDFFormat.TURTLE;
-        }
+        return switch (name) {
+            case "text/plain" -> RDFFormat.TURTLE;
+            case "text/turtle" -> RDFFormat.TURTLE;
+            case "application/ld+json" -> RDFFormat.JSONLD;
+            case "application/rdf+xml" -> RDFFormat.RDFXML;
+            case "text/n3" -> RDFFormat.N3;
+            default -> RDFFormat.TURTLE;
+        };
+    }
+
+    public static String getToken(HttpServletRequest req) {
+        return ofNullable(req.getHeader(HttpHeaders.AUTHORIZATION))
+                .filter(h -> h.startsWith("Bearer "))
+                .flatMap(h -> of(h.substring(7)))
+                .orElse(null);
     }
 
     public static String removeLastSlash(String url) {
diff --git a/src/main/java/nl/dtls/fairdatapoint/util/RdfIOUtil.java b/src/main/java/nl/dtls/fairdatapoint/util/RdfIOUtil.java
index 6b9f444e151a7837522a741e20f65741fdb5fa67..b0dbcdb8c0ebc5b16f698c8715d664ebb84ada7a 100755
--- a/src/main/java/nl/dtls/fairdatapoint/util/RdfIOUtil.java
+++ b/src/main/java/nl/dtls/fairdatapoint/util/RdfIOUtil.java
@@ -27,7 +27,12 @@ import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.model.Resource;
 import org.eclipse.rdf4j.model.Statement;
 import org.eclipse.rdf4j.model.impl.LinkedHashModel;
+import org.eclipse.rdf4j.model.vocabulary.DCAT;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.FOAF;
+import org.eclipse.rdf4j.model.vocabulary.LDP;
 import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.XMLSchema;
 import org.eclipse.rdf4j.rio.*;
 import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
 
@@ -58,9 +63,15 @@ public class RdfIOUtil {
                 .orElseThrow(() -> new ValidationException("Validation failed (no rdf:type was provided"));
         // - sanitize statements
         List<Statement> sanitizedStatements =
-                new ArrayList<>(oldModel.filter(oldBaseUri, null, null))
+                new ArrayList<>(oldModel)
                         .stream()
-                        .map(oldStatement -> s(i(newBaseUri), oldStatement.getPredicate(), oldStatement.getObject()))
+                        .map(oldStatement -> {
+                            if (oldStatement.getSubject().stringValue().equals(oldBaseUri.stringValue())) {
+                                return s(i(newBaseUri), oldStatement.getPredicate(), oldStatement.getObject());
+                            } else {
+                                return oldStatement;
+                            }
+                        })
                         .collect(Collectors.toList());
         Model model = new LinkedHashModel();
         model.addAll(sanitizedStatements);
@@ -105,6 +116,12 @@ public class RdfIOUtil {
     }
 
     public static String write(Model model, RDFFormat format) {
+        model.setNamespace(DCTERMS.NS);
+        model.setNamespace(DCAT.NS);
+        model.setNamespace(FOAF.NS);
+        model.setNamespace(XMLSchema.NS);
+        model.setNamespace(LDP.NS);
+
         try (StringWriter out = new StringWriter()) {
             Rio.write(model, out, format, getWriterConfig());
             return out.toString();
diff --git a/src/main/java/nl/dtls/fairdatapoint/util/RdfUtil.java b/src/main/java/nl/dtls/fairdatapoint/util/RdfUtil.java
index 23281f2b7878b59525a8d4836f54c2d54fe13e07..706eacff0b438a12c3b6b8849f5d31c18e75526c 100755
--- a/src/main/java/nl/dtls/fairdatapoint/util/RdfUtil.java
+++ b/src/main/java/nl/dtls/fairdatapoint/util/RdfUtil.java
@@ -77,18 +77,20 @@ public class RdfUtil {
     /****************************************************
      *** Update
      ****************************************************/
-    public static void update(Model m, Resource subj, IRI pred, Value obj, Resource... contexts) {
-        m.remove(subj, pred, null, contexts);
+    public static void update(Model m, Resource subj, IRI pred, Value obj) {
+        m.remove(subj, pred, null);
         if (subj != null && pred != null && obj != null) {
-            m.add(subj, pred, obj, contexts);
+            m.add(subj, pred, obj);
         }
     }
 
-    public static <T extends Value> void update(Model m, Resource subj, IRI pred, List<T> list, Resource... contexts) {
-        m.remove(subj, pred, null, contexts);
-        list.forEach(obj -> {
-            m.add(subj, pred, obj, contexts);
-        });
+    public static <T extends Value> void update(Model m, Resource subj, IRI pred, List<T> list) {
+        m.remove(subj, pred, null);
+        if (list != null) {
+            list.forEach(obj -> {
+                m.add(subj, pred, obj);
+            });
+        }
     }
 
     /****************************************************
diff --git a/src/main/java/nl/dtls/fairdatapoint/util/ValidationUtil.java b/src/main/java/nl/dtls/fairdatapoint/util/ValidationUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..b55c7db5fdaa99c22fe51fcc62d9494967a8811c
--- /dev/null
+++ b/src/main/java/nl/dtls/fairdatapoint/util/ValidationUtil.java
@@ -0,0 +1,42 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.util;
+
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.validation.BindException;
+
+public class ValidationUtil {
+
+    public static <T> void validationFailed(String field, String code, String defaultMessage, T reqDto) throws BindException {
+        BeanPropertyBindingResult error = new BeanPropertyBindingResult(reqDto, reqDto.getClass().getName());
+        error.rejectValue(field, code, defaultMessage);
+        throw new BindException(error);
+    }
+
+    public static <T> void uniquenessValidationFailed(String field, T reqDto) throws BindException {
+        BeanPropertyBindingResult error = new BeanPropertyBindingResult(reqDto, reqDto.getClass().getName());
+        error.rejectValue(field, "Uniqueness", "must be unique");
+        throw new BindException(error);
+    }
+
+}
diff --git a/src/main/java/nl/dtls/fairdatapoint/util/ValueFactoryHelper.java b/src/main/java/nl/dtls/fairdatapoint/util/ValueFactoryHelper.java
index 5baa1e2cb6fe73773ee2cd17de844d03d6911e6a..eae3d1b9f83fdcabd945b5f527422a02b777f66d 100755
--- a/src/main/java/nl/dtls/fairdatapoint/util/ValueFactoryHelper.java
+++ b/src/main/java/nl/dtls/fairdatapoint/util/ValueFactoryHelper.java
@@ -24,8 +24,9 @@ package nl.dtls.fairdatapoint.util;
 
 import org.eclipse.rdf4j.model.*;
 import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.model.vocabulary.XMLSchema;
 
-import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Optional;
 
@@ -98,8 +99,11 @@ public class ValueFactoryHelper {
         return VF.createLiteral(literal);
     }
 
-    public static Literal l(LocalDateTime literal) {
-        return VF.createLiteral(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(literal));
+    public static Literal l(OffsetDateTime literal) {
+        if (literal == null) {
+            return null;
+        }
+        return VF.createLiteral(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(literal), XMLSchema.DATETIME);
     }
 
     public static Literal l(Value value) {
@@ -116,4 +120,8 @@ public class ValueFactoryHelper {
     public static Statement s(Resource subject, IRI predicate, Value object, Resource context) {
         return VF.createStatement(subject, predicate, object, context);
     }
+
+    public static Resource bn() {
+        return VF.createBNode();
+    }
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/WebIntegrationTest.java b/src/test/java/nl/dtls/fairdatapoint/WebIntegrationTest.java
index b348b92dbe4de9d58d803a7dda4dce7984133fbd..fc47bde4890b0f7fdce1cab4cd3ba172392bc746 100755
--- a/src/test/java/nl/dtls/fairdatapoint/WebIntegrationTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/WebIntegrationTest.java
@@ -23,37 +23,35 @@
 package nl.dtls.fairdatapoint;
 
 import nl.dtls.fairdatapoint.database.mongo.migration.development.MigrationRunner;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.RdfDevelopmentMigrationRunner;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.web.client.TestRestTemplate;
-import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
 @ExtendWith(SpringExtension.class)
 @ActiveProfiles(Profiles.TESTING)
-// TODO Remove @DirtiesContext annotation
-@DirtiesContext
 @SpringBootTest(
         webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
         properties = {"spring.main.allow-bean-definition-overriding=true"})
 @AutoConfigureMockMvc
 public abstract class WebIntegrationTest {
 
-    public static final String ADMIN_TOKEN = "Bearer eyJhbGciOiJIUzI1NiJ9" +
-            ".eyJzdWIiOiI5NTU4OWU1MC1kMjYxLTQ5MmItODg1Mi05MzI0ZTlhNjZhNDIiLCJpYXQiOjE1NzQyNTE5OTAsImV4cCI6MjQzODE2NTU5MH0" +
-            ".KHcGQqqTOzC9Xqzj07PRuTDa__c1BDC9obb-DKsSaQo";
+    public static final String ADMIN_TOKEN = "Bearer eyJhbGciOiJIUzUxMiJ9" +
+            ".eyJzdWIiOiI5NTU4OWU1MC1kMjYxLTQ5MmItODg1Mi05MzI0ZTlhNjZhNDIiLCJpYXQiOjE2MjA4Mzg3NjUsImV4cCI6MjUzMzcwNzY4NDYxfQ" +
+            ".hF8SnFH_1m00bjQOja77OzPgpPbX-wJH8RUdcOOR7F-QrTRCqwOdrqDfgN1lFW0XrrIljIvYqCo20pcYTvh2Dw";
 
-    public static final String ALBERT_TOKEN = "Bearer eyJhbGciOiJIUzI1NiJ9" +
-            ".eyJzdWIiOiI3ZTY0ODE4ZC02Mjc2LTQ2ZmItOGJiMS03MzJlNmUwOWY3ZTkiLCJpYXQiOjE1NzI0NDczNTksImV4cCI6MjQzNjM2MDk1OX0" +
-            ".yGZthRlVezhbKk1gDymW6pZfbCoxxqJda6md9btp00w";
+    public static final String ALBERT_TOKEN = "Bearer eyJhbGciOiJIUzUxMiJ9" +
+            ".eyJzdWIiOiI3ZTY0ODE4ZC02Mjc2LTQ2ZmItOGJiMS03MzJlNmUwOWY3ZTkiLCJpYXQiOjE2MjA4Mzg3NDUsImV4cCI6MjUzMzcwNzY4NDYxfQ" +
+            ".jLq89vH-YVPzKDSe44dV8CA2jpb8Or_xPf2gboiwaMTZwF_riNaVGJaziw8uYHRAIMb4bFBBd6MHbDiwrLlZZg";
 
-    public static final String NIKOLA_TOKEN = "Bearer eyJhbGciOiJIUzI1NiJ9" +
-            ".eyJzdWIiOiJiNWI5MmM2OS01ZWQ5LTQwNTQtOTU0ZC0wMTIxYzI5YjY4MDAiLCJpYXQiOjE1NzI5NjU2NTksImV4cCI6MjQzNjg3OTI1OX0" +
-            ".f-nAX35Ob392xzerVqN9j34kCorZ0Lu6I18OgflHROs";
+    public static final String NIKOLA_TOKEN = "Bearer eyJhbGciOiJIUzUxMiJ9" +
+            ".eyJzdWIiOiJiNWI5MmM2OS01ZWQ5LTQwNTQtOTU0ZC0wMTIxYzI5YjY4MDAiLCJpYXQiOjE2MjA4Mzg3MDgsImV4cCI6MjUzMzcwNzY4NDYxfQ" +
+            ".U3mPUE0fREeVlresvl6uHR-aTj3ATFYn7CsAJ0cyOhqvaICTvURewF8QPfw2WVZ4GGc8Ej46BqHI9rpwKqRxpQ";
 
     @Autowired
     protected TestRestTemplate client;
@@ -61,9 +59,13 @@ public abstract class WebIntegrationTest {
     @Autowired
     protected MigrationRunner migrationRunner;
 
+    @Autowired
+    protected RdfDevelopmentMigrationRunner rdfDevelopmentMigrationRunner;
+
     @BeforeEach
     public void setup() {
         migrationRunner.run();
+        rdfDevelopmentMigrationRunner.run();
     }
 
 }
\ No newline at end of file
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/actuator/ActuatorInfoDTO.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/actuator/ActuatorInfoDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..a595bc9f6ffcedba191b344c94c77a579adafedb
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/actuator/ActuatorInfoDTO.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.actuator;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ActuatorInfoDTO {
+    @NotNull
+    @NotBlank
+    private String name;
+
+    @NotNull
+    @NotBlank
+    private String version;
+
+    @NotNull
+    @NotBlank
+    private String builtAt;
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/actuator/List_Info_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/actuator/List_Info_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b17817ed42649f6499da54924e4fdab971ee11f
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/actuator/List_Info_GET.java
@@ -0,0 +1,82 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.actuator;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+public class List_Info_GET extends WebIntegrationTest {
+
+    @Value("${git.branch}")
+    private String branch;
+
+    @Value("${git.commit.id.abbrev}")
+    private String commitShort;
+
+    @Value("${git.tags}")
+    private String tag;
+
+    @Value("${build.time}")
+    private String buildTime;
+
+    private URI url() {
+        return URI.create("/actuator/info");
+    }
+
+    @Test
+    public void res200() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url())
+                .build();
+        ParameterizedTypeReference<ActuatorInfoDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ActuatorInfoDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(notNullValue()));
+        if (tag == null || tag.isEmpty()) {
+            assertThat(result.getBody().getVersion(), is(equalTo((format("%s~%s", branch, commitShort)))));
+        } else {
+            assertThat(result.getBody().getVersion(), is(equalTo((format("%s~%s", tag, commitShort)))));
+        }
+        assertThat(result.getBody().getBuiltAt(), is(equalTo(buildTime)));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/Detail_DELETE.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/Detail_DELETE.java
new file mode 100644
index 0000000000000000000000000000000000000000..a703089c238380739956afb79895690c1b972df2
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/Detail_DELETE.java
@@ -0,0 +1,96 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.apikey;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.apikey.data.ApiKeyFixtures;
+import nl.dtls.fairdatapoint.entity.apikey.ApiKey;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestDelete;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createUserForbiddenTestDelete;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createAdminNotFoundTestDelete;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("DELETE /api-keys/:apikeyUuid")
+public class Detail_DELETE extends WebIntegrationTest {
+
+    private URI url(String uuid) {
+        return URI.create(format("/api-keys/%s", uuid));
+    }
+
+    @Autowired
+    private ApiKeyFixtures apikeyFixtures;
+
+    @Test
+    @DisplayName("HTTP 204")
+    public void res204() {
+        // GIVEN:
+        ApiKey apikey = apikeyFixtures.albertApiKey();
+        RequestEntity<Void> request = RequestEntity
+                .delete(url(apikey.getUuid()))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .build();
+        ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        ApiKey apikey = apikeyFixtures.albertApiKey();
+        createNoUserForbiddenTestDelete(client, url(apikey.getUuid()));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not an owner")
+    public void res403_apikey() {
+        ApiKey apikey = apikeyFixtures.nikolaApiKey();
+        createUserForbiddenTestDelete(client, url(apikey.getUuid()));
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createAdminNotFoundTestDelete(client, url("nonExisting"));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Member_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/List_GET.java
old mode 100755
new mode 100644
similarity index 63%
rename from src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Member_GET.java
rename to src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/List_GET.java
index c3d35fceef3c287e7e48cb773b8461b8ed6d1707..e775328d20807a8f0378882063975d8a60486e30
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Member_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/List_GET.java
@@ -20,12 +20,14 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-package nl.dtls.fairdatapoint.acceptance.metadata.repository;
+package nl.dtls.fairdatapoint.acceptance.apikey;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
-import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.apikey.ApiKeyDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.apikey.data.ApiKeyFixtures;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
@@ -33,19 +35,23 @@ import org.springframework.http.RequestEntity;
 import org.springframework.http.ResponseEntity;
 
 import java.net.URI;
+import java.util.List;
 
-import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyMember;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestGet;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 
-@DisplayName("GET /member")
-public class Detail_Member_GET extends WebIntegrationTest {
+@DisplayName("GET /api-keys")
+public class List_GET extends WebIntegrationTest {
 
     private URI url() {
-        return URI.create("/member");
+        return URI.create("/api-keys");
     }
 
+    @Autowired
+    private ApiKeyFixtures apikeyFixtures;
+
     @Test
     @DisplayName("HTTP 200")
     public void res200() {
@@ -53,36 +59,24 @@ public class Detail_Member_GET extends WebIntegrationTest {
         RequestEntity<Void> request = RequestEntity
                 .get(url())
                 .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
-                .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<List<ApiKeyDTO>> responseType = new ParameterizedTypeReference<>() {
         };
 
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<List<ApiKeyDTO>> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertEmptyMember(result.getBody());
+        List<ApiKeyDTO> body = result.getBody();
+        assertThat(body.size(), is(equalTo(1)));
+        body.get(0).equals(apikeyFixtures.albertApiKey());
     }
 
     @Test
-    @DisplayName("HTTP 200: No user")
-    public void res200_no_user() {
-        // GIVEN:
-        RequestEntity<Void> request = RequestEntity
-                .get(url())
-                .header(HttpHeaders.ACCEPT, "application/json")
-                .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
-        };
-
-        // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
-
-        // THEN:
-        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertEmptyMember(result.getBody());
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestGet(client, url());
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/List_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..c313892afeb47ea24fc3aa1becba0c82c74477d8
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/apikey/List_POST.java
@@ -0,0 +1,71 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.apikey;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.apikey.ApiKeyDTO;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPost;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("POST /api-keys")
+public class List_POST extends WebIntegrationTest {
+
+    private URI url() {
+        return URI.create("/api-keys");
+    }
+
+    @Test
+    @DisplayName("HTTP 201")
+    public void res201() {
+        // GIVEN: Prepare request
+        RequestEntity<Void> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(null);
+        ParameterizedTypeReference<ApiKeyDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ApiKeyDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.CREATED)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPost(client, url(), null);
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/general/SecurityTest.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/general/SecurityTest.java
index 4ec602fe34c5aaecaeaecc69e3dd0d3d70c9d559..52b9566ec505898241142ea0fc8534aaf71a4f50 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/general/SecurityTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/general/SecurityTest.java
@@ -23,8 +23,9 @@
 package nl.dtls.fairdatapoint.acceptance.general;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.apikey.data.ApiKeyFixtures;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.rio.RDFFormat;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -36,6 +37,7 @@ import org.springframework.http.ResponseEntity;
 
 import java.net.URI;
 
+import static java.lang.String.format;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
@@ -43,7 +45,10 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class SecurityTest extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
+
+    @Autowired
+    private ApiKeyFixtures apiKeyFixtures;
 
     @Test
     public void postRequestsAreSecured() {
@@ -54,11 +59,11 @@ public class SecurityTest extends WebIntegrationTest {
                 .post(URI.create("/distribution"))
                 .header(HttpHeaders.CONTENT_TYPE, "text/turtle")
                 .body(reqDto);
-        ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
         };
 
         // WHEN:
-        ResponseEntity<Void> result = client.exchange(request, responseType);
+        ResponseEntity<String> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
@@ -83,4 +88,25 @@ public class SecurityTest extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
     }
 
+    @Test
+    public void apiKeyIsWorking() {
+        // GIVEN: Prepare data
+        String reqDto = RdfIOUtil.write(testMetadataFixtures.c1_d1_distribution1(), RDFFormat.TURTLE);
+        // AND: Prepare request
+        RequestEntity<String> request = RequestEntity
+                .post(URI.create("/distribution"))
+                .header(HttpHeaders.CONTENT_TYPE, "text/turtle")
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .header(HttpHeaders.AUTHORIZATION, format("Bearer %s", apiKeyFixtures.ALBERT_API_KEY))
+                .body(reqDto);
+        ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.CREATED)));
+    }
+
 }
\ No newline at end of file
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/admin/List_TriggerAll_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/admin/List_TriggerAll_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..307005646eb930788692fa1699b658f1f8c8425b
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/admin/List_TriggerAll_POST.java
@@ -0,0 +1,130 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.admin;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.EventRepository;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.EventType;
+import nl.dtls.fairdatapoint.utils.TestIndexEntryFixtures;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("POST /index/admin/trigger-all")
+public class List_TriggerAll_POST extends WebIntegrationTest {
+
+    @Autowired
+    private EventRepository eventRepository;
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    private final ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+    };
+
+    private URI url() {
+        return URI.create("/index/admin/trigger-all");
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN
+        RequestEntity<Void> request = RequestEntity
+                .post(url())
+                .build();
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: incorrect token")
+    public void res403_incorrectToken() {
+        // GIVEN
+        RequestEntity<Void> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, "mySecretToken")
+                .build();
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: non-admin token")
+    public void res403_nonAdminToken() {
+        // GIVEN
+        RequestEntity<Void> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .build();
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 204: trigger all")
+    public void res204_triggerAll() {
+        // GIVEN: prepare request
+        RequestEntity<Void> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .build();
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+        List<Event> events = eventRepository.getAllByType(EventType.AdminTrigger);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+        assertThat("One AdminTrigger event is created", events.size(), is(equalTo(1)));
+        assertThat("Records correct client URL as null", events.get(0).getAdminTrigger().getClientUrl(), is(equalTo(null)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/admin/List_Trigger_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/admin/List_Trigger_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..65103618136adee6f2cff7eab061d392c8fb1249
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/admin/List_Trigger_POST.java
@@ -0,0 +1,198 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.admin;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.EventRepository;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.event.Event;
+import nl.dtls.fairdatapoint.entity.index.event.EventType;
+import nl.dtls.fairdatapoint.utils.TestIndexEntryFixtures;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("POST /index/admin/trigger")
+public class List_Trigger_POST extends WebIntegrationTest {
+
+    @Autowired
+    private EventRepository eventRepository;
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    private final ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+    };
+
+    private URI url() {
+        return URI.create("/index/admin/trigger");
+    }
+
+    private PingDTO reqDTO(String clientUrl) {
+        PingDTO dto = new PingDTO();
+        dto.setClientUrl(clientUrl);
+        return dto;
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN: prepare data
+        String clientUrl = "http://example.com";
+        PingDTO reqDTO = reqDTO(clientUrl);
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: incorrect token")
+    public void res403_incorrectToken() {
+        // GIVEN: prepare data
+        String clientUrl = "http://example.com";
+        PingDTO reqDTO = reqDTO(clientUrl);
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, "mySecretToken")
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: non-admin token")
+    public void res403_nonAdminToken() {
+        // GIVEN: prepare data
+        String clientUrl = "http://example.com";
+        PingDTO reqDTO = reqDTO(clientUrl);
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: malformed URL")
+    public void res403_malformedUrl() {
+        // GIVEN: prepare data
+        String clientUrl = "http://example.com";
+        PingDTO reqDTO = reqDTO(clientUrl);
+        reqDTO.setClientUrl("thisIsNot/Url");
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+    }
+
+    @Test
+    @DisplayName("HTTP 204: trigger one")
+    public void res204_triggerOne() {
+        // GIVEN: prepare data
+        IndexEntry entry = TestIndexEntryFixtures.entryExample();
+        indexEntryRepository.save(entry);
+        PingDTO reqDTO = reqDTO(entry.getClientUrl());
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+        List<Event> events = eventRepository.getAllByType(EventType.AdminTrigger);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+        assertThat("One AdminTrigger event is created", events.size(), is(equalTo(1)));
+        assertThat("Records correct client URL", events.get(0).getAdminTrigger().getClientUrl(), is(equalTo(entry.getClientUrl())));
+    }
+
+    @Test
+    @DisplayName("HTTP 404: trigger non-existing")
+    public void res404_triggerOne() {
+        // GIVEN: prepare data
+        IndexEntry entry = TestIndexEntryFixtures.entryExample();
+        PingDTO reqDTO = reqDTO(entry.getClientUrl());
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+        List<Event> events = eventRepository.getAllByType(EventType.AdminTrigger);
+
+        // THEN: 
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+        assertThat("One AdminTrigger event is created", events.size(), is(equalTo(1)));
+        assertThat("Records correct client URL", events.get(0).getAdminTrigger().getClientUrl(), is(equalTo(entry.getClientUrl())));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_All_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_All_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..06821ff6cbdbdd5d12937a0fe88e29d711565a2a
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_All_GET.java
@@ -0,0 +1,137 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.entry;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.utils.TestIndexEntryFixtures;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /index/entries/all")
+public class List_All_GET extends WebIntegrationTest {
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    private final ParameterizedTypeReference<List<IndexEntryDTO>> responseType = new ParameterizedTypeReference<>() {
+    };
+
+    private URI url() {
+        return URI.create("/index/entries/all");
+    }
+
+    @Test
+    @DisplayName("HTTP 200: list empty")
+    public void res200_listEmpty() {
+        // GIVEN: prepare data
+        indexEntryRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN:
+        ResponseEntity<List<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("There are no entries in the response", result.getBody().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: list few")
+    public void res200_listFew() {
+        // GIVEN: prepare data
+        indexEntryRepository.deleteAll();
+        List<IndexEntry> entries = TestIndexEntryFixtures.entriesFew();
+        indexEntryRepository.saveAll(entries);
+        int size = 9;
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<List<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Correct number of entries is in the response", result.getBody().size(),
+                is(equalTo(entries.size())));
+        for (int i = 0; i < entries.size(); i++) {
+            assertThat("Entry matches: " + entries.get(i).getClientUrl(), result.getBody().get(i).getClientUrl(),
+                    is(equalTo(entries.get(i).getClientUrl())));
+        }
+    }
+
+    @Test
+    @DisplayName("HTTP 200: list many")
+    public void res200_listMany() {
+        // GIVEN: prepare data
+        indexEntryRepository.deleteAll();
+        List<IndexEntry> entries = TestIndexEntryFixtures.entriesN(300);
+        indexEntryRepository.saveAll(entries);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<List<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Correct number of entries is in the response", result.getBody().size(),
+                is(equalTo(entries.size())));
+        for (int i = 0; i < entries.size(); i++) {
+            assertThat("Entry matches: " + entries.get(i).getClientUrl(), result.getBody().get(i).getClientUrl(),
+                    is(equalTo(entries.get(i).getClientUrl())));
+        }
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..40e834e7d8147da417973bd3df83f3e39aee7930
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_GET.java
@@ -0,0 +1,226 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.entry;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.utils.CustomPageImpl;
+import nl.dtls.fairdatapoint.utils.TestIndexEntryFixtures;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /index/entries")
+public class List_GET extends WebIntegrationTest {
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    private final ParameterizedTypeReference<CustomPageImpl<IndexEntryDTO>> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/index/entries");
+    }
+
+    private URI urlWithPage(int page) {
+        return UriComponentsBuilder.fromUri(url())
+                .queryParam("page", page)
+                .build().toUri();
+    }
+
+    private URI urlWithPageSize(int page, int size) {
+        return UriComponentsBuilder.fromUri(url())
+                .queryParam("page", page)
+                .queryParam("size", size)
+                .build().toUri();
+    }
+
+    @Test
+    @DisplayName("HTTP 200: page empty")
+    public void res200_pageEmpty() {
+        // GIVEN: prepare data
+        indexEntryRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<CustomPageImpl<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Current page is the first page", result.getBody().isFirst(), is(Boolean.TRUE));
+        assertThat("Current page is the last page", result.getBody().isLast(), is(Boolean.TRUE));
+        assertThat("Current page is empty", result.getBody().isEmpty(), is(Boolean.TRUE));
+        assertThat("Number of pages is 0", result.getBody().getTotalPages(), is(equalTo(0)));
+        assertThat("Number of elements is 0", result.getBody().getTotalElements(), is(equalTo(0L)));
+        assertThat("There are no entries in the response", result.getBody().getContent().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: out-of-bounds page")
+    public void res200_outOfBoundsPage() {
+        // GIVEN: prepare data
+        indexEntryRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(urlWithPage(7))
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN:
+        ResponseEntity<CustomPageImpl<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Current page is not the first page", result.getBody().isFirst(), is(Boolean.FALSE));
+        assertThat("Current page is the last page", result.getBody().isLast(), is(Boolean.TRUE));
+        assertThat("Current page is empty", result.getBody().isEmpty(), is(Boolean.TRUE));
+        assertThat("Number of pages is 0", result.getBody().getTotalPages(), is(equalTo(0)));
+        assertThat("Number of elements is 0", result.getBody().getTotalElements(), is(equalTo(0L)));
+        assertThat("There are no entries in the response", result.getBody().getContent().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: page few")
+    public void res200_pageFew() {
+        // GIVEN: prepare data
+        indexEntryRepository.deleteAll();
+        List<IndexEntry> entries = TestIndexEntryFixtures.entriesFew();
+        indexEntryRepository.saveAll(entries);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<CustomPageImpl<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Current page is the first page", result.getBody().isFirst(), is(Boolean.TRUE));
+        assertThat("Current page is the last page", result.getBody().isLast(), is(Boolean.TRUE));
+        assertThat("Current page is not empty", result.getBody().isEmpty(), is(Boolean.FALSE));
+        assertThat("Number of pages is 1", result.getBody().getTotalPages(), is(equalTo(1)));
+        assertThat("Number of elements is correct", result.getBody().getTotalElements(),
+                is(equalTo(Integer.toUnsignedLong(entries.size()))));
+        assertThat("There is correct number of entries in the response", result.getBody().getContent().size(),
+                is(equalTo(entries.size())));
+        for (int i = 0; i < entries.size(); i++) {
+            assertThat("Entry matches: " + entries.get(i).getClientUrl(),
+                    result.getBody().getContent().get(i).getClientUrl(), is(equalTo(entries.get(i).getClientUrl())));
+        }
+    }
+
+    @Test
+    @DisplayName("HTTP 200: page many (middle)")
+    public void res200_pageManyMiddle() {
+        // GIVEN: prepare data
+        long items = 300L;
+        int size = 30;
+        int page = 3;
+        indexEntryRepository.deleteAll();
+        List<IndexEntry> entries = TestIndexEntryFixtures.entriesN(items);
+        indexEntryRepository.saveAll(entries);
+
+        // AND (prepare request)
+        RequestEntity<?> request = RequestEntity
+                .get(urlWithPageSize(page, size))
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<CustomPageImpl<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Current page is the first page", result.getBody().isFirst(), is(Boolean.FALSE));
+        assertThat("Current page is the last page", result.getBody().isLast(), is(Boolean.FALSE));
+        assertThat("Current page is not empty", result.getBody().isEmpty(), is(Boolean.FALSE));
+        assertThat("Number of pages is correct", result.getBody().getTotalPages(), is(equalTo(10)));
+        assertThat("Number of elements is correct", result.getBody().getTotalElements(), is(equalTo(items)));
+        assertThat("There is correct number of entries in the response", result.getBody().getContent().size(),
+                is(equalTo(size)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: page many (last)")
+    public void res200_pageManyLast() {
+        // GIVEN (prepare data)
+        long items = 666;
+        int size = 300;
+        int lastSize = 66;
+        int page = 2;
+        indexEntryRepository.deleteAll();
+        List<IndexEntry> entries = TestIndexEntryFixtures.entriesN(items);
+        indexEntryRepository.saveAll(entries);
+
+        // AND (prepare request)
+        RequestEntity<?> request = RequestEntity
+                .get(urlWithPageSize(page, size))
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<CustomPageImpl<IndexEntryDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Current page is the first page", result.getBody().isFirst(), is(Boolean.FALSE));
+        assertThat("Current page is the last page", result.getBody().isLast(), is(Boolean.TRUE));
+        assertThat("Current page is not empty", result.getBody().isEmpty(), is(Boolean.FALSE));
+        assertThat("Number of pages is correct", result.getBody().getTotalPages(), is(equalTo(3)));
+        assertThat("Number of elements is correct", result.getBody().getTotalElements(), is(equalTo(items)));
+        assertThat("There is correct number of entries in the response", result.getBody().getContent().size(),
+                is(equalTo(lastSize)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_Info_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_Info_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..29c2ce0c735bc06af06e72bae342aa307ed24779
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/entry/List_Info_GET.java
@@ -0,0 +1,81 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.entry;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryInfoDTO;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.HashMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /index/entries/info")
+public class List_Info_GET extends WebIntegrationTest {
+
+    private final ParameterizedTypeReference<IndexEntryInfoDTO> responseType = new ParameterizedTypeReference<>() {
+    };
+
+    private URI url() {
+        return URI.create("/index/entries/info");
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200_listMany() {
+        // GIVEN: Prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // AND: Prepare expectation
+        IndexEntryInfoDTO expDto = new IndexEntryInfoDTO(new HashMap<>() {{
+            put("ALL", 6L);
+            put("ACTIVE", 1L);
+            put("INACTIVE", 2L);
+            put("UNKNOWN", 1L);
+            put("INVALID", 1L);
+            put("UNREACHABLE", 1L);
+        }});
+
+        // WHEN:
+        ResponseEntity<IndexEntryInfoDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Correct number of entries is in the response", result.getBody(),
+                is(equalTo(expDto)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/ping/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/ping/List_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..c219514424876ce7450424d4531c6574006cf1e8
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/ping/List_POST.java
@@ -0,0 +1,173 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.ping;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.ping.PingDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexEntryRepository;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.utils.TestIndexEntryFixtures;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.HashMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("POST /")
+public class List_POST extends WebIntegrationTest {
+
+    @Autowired
+    private IndexEntryRepository indexEntryRepository;
+
+    private final ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+    };
+
+    private URI url() {
+        return URI.create("/");
+    }
+
+    private PingDTO reqDTO(String clientUrl) {
+        PingDTO dto = new PingDTO();
+        dto.setClientUrl(clientUrl);
+        return dto;
+    }
+
+    @Test
+    @DisplayName("HTTP 204: new entry")
+    public void res204_newEntry() {
+        // GIVEN: prepare data
+        String clientUrl = "http://example.com";
+        PingDTO reqDto = reqDTO(clientUrl);
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+
+        // WHEN:
+        assertThat("Entry does not exist before the ping",
+                indexEntryRepository.findByClientUrl(clientUrl).isPresent(), is(Boolean.FALSE));
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+        assertThat("Entry exists after the ping", indexEntryRepository.findByClientUrl(clientUrl).isPresent(),
+                is(Boolean.TRUE));
+    }
+
+    @Test
+    @DisplayName("HTTP 204: existing entry")
+    public void res204_existingEnty() {
+        // GIVEN: prepare data
+        IndexEntry indexEntry = TestIndexEntryFixtures.entryExample();
+        String clientUrl = indexEntry.getClientUrl();
+        PingDTO reqDto = reqDTO(clientUrl);
+
+        // AND: prepare request
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+
+        // WHEN:
+        indexEntryRepository.save(indexEntry);
+        assertThat("Entry exists before the ping", indexEntryRepository.findByClientUrl(clientUrl).isPresent(),
+                is(Boolean.TRUE));
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+        assertThat("Entry exists after the ping", indexEntryRepository.findByClientUrl(clientUrl).isPresent(),
+                is(Boolean.TRUE));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: null client url")
+    public void res400_nullClientUrl() {
+        // GIVEN (prepare data)
+        PingDTO reqDto = reqDTO(null);
+
+        // AND (prepare request)
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: non-URL client url")
+    public void res400_nonUrlClientUrl() {
+        // GIVEN (prepare data)
+        PingDTO reqDto = reqDTO("testing");
+
+        // AND (prepare request)
+        RequestEntity<PingDTO> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: different body")
+    public void res400_differentBody() {
+        // GIVEN (prepare data)
+        HashMap<String, String> dummyData = new HashMap<>();
+        dummyData.put("content", "http://test");
+
+        // AND (prepare request)
+        RequestEntity<HashMap<String, String>> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .body(dummyData);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_DELETE.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_DELETE.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fd318fa53107897b296134fb179b4ffa4d0892f
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_DELETE.java
@@ -0,0 +1,175 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.settings;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexSettingsRepository;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettings;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsPing;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsRetrieval;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("DELETE /index/settings")
+public class List_DELETE extends WebIntegrationTest {
+
+    @Autowired
+    private IndexSettingsRepository indexSettingsRepository;
+
+    private final ParameterizedTypeReference<IndexSettingsDTO> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/index/settings");
+    }
+
+    private IndexSettings customSettings() {
+        return new IndexSettings()
+                .toBuilder()
+                .ping(
+                        new IndexSettingsPing()
+                                .toBuilder()
+                                .denyList(Collections.singletonList("http://localhost.*$"))
+                                .rateLimitDuration(Duration.ofMinutes(17))
+                                .validDuration(Duration.ofDays(5))
+                                .rateLimitHits(666)
+                                .build()
+                )
+                .retrieval(
+                        new IndexSettingsRetrieval()
+                                .toBuilder()
+                                .rateLimitWait(Duration.ofHours(16))
+                                .timeout(Duration.ofSeconds(55))
+                                .build()
+                )
+                .build();
+    }
+
+    @Test
+    @DisplayName("HTTP 200: default settings")
+    public void res200_defaultSettings() {
+        // GIVEN: prepare data
+        IndexSettings settings = IndexSettings.getDefault();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default valid duration", Objects.requireNonNull(result.getBody()).getPing().getValidDuration(), is(equalTo(settings.getPing().getValidDuration().toString())));
+        assertThat("Response contains default rate limit duration", Objects.requireNonNull(result.getBody()).getPing().getRateLimitDuration(), is(equalTo(settings.getPing().getRateLimitDuration().toString())));
+        assertThat("Response contains default rate limit hits", Objects.requireNonNull(result.getBody()).getPing().getRateLimitHits(), is(equalTo(settings.getPing().getRateLimitHits())));
+        assertThat("Response contains default deny list", Objects.requireNonNull(result.getBody()).getPing().getDenyList(), is(equalTo(settings.getPing().getDenyList())));
+        assertThat("Response contains default timeout", Objects.requireNonNull(result.getBody()).getRetrieval().getTimeout(), is(equalTo(settings.getRetrieval().getTimeout().toString())));
+        assertThat("Response contains default rate limit wait", Objects.requireNonNull(result.getBody()).getRetrieval().getRateLimitWait(), is(equalTo(settings.getRetrieval().getRateLimitWait().toString())));
+        assertThat("Response indicated default settings", Objects.requireNonNull(result.getBody()).getIsDefault(), is(Boolean.TRUE));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: custom settings")
+    public void res200_customSettings() {
+        // GIVEN: prepare data
+        IndexSettings settings = IndexSettings.getDefault();
+        IndexSettings customSettings = customSettings();
+        indexSettingsRepository.deleteAll();
+        indexSettingsRepository.insert(customSettings);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default valid duration", Objects.requireNonNull(result.getBody()).getPing().getValidDuration(), is(equalTo(settings.getPing().getValidDuration().toString())));
+        assertThat("Response contains default rate limit duration", Objects.requireNonNull(result.getBody()).getPing().getRateLimitDuration(), is(equalTo(settings.getPing().getRateLimitDuration().toString())));
+        assertThat("Response contains default rate limit hits", Objects.requireNonNull(result.getBody()).getPing().getRateLimitHits(), is(equalTo(settings.getPing().getRateLimitHits())));
+        assertThat("Response contains default deny list", Objects.requireNonNull(result.getBody()).getPing().getDenyList(), is(equalTo(settings.getPing().getDenyList())));
+        assertThat("Response contains default timeout", Objects.requireNonNull(result.getBody()).getRetrieval().getTimeout(), is(equalTo(settings.getRetrieval().getTimeout().toString())));
+        assertThat("Response contains default rate limit wait", Objects.requireNonNull(result.getBody()).getRetrieval().getRateLimitWait(), is(equalTo(settings.getRetrieval().getRateLimitWait().toString())));
+        assertThat("Response indicated default settings", Objects.requireNonNull(result.getBody()).getIsDefault(), is(Boolean.TRUE));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden without auth", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: not admin")
+    public void res403_notAdmin() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .header(HttpHeaders.AUTHORIZATION, NIKOLA_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden for non-admin users", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..5748308271101daa49f9b92cfa0803fb7d169d88
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_GET.java
@@ -0,0 +1,176 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.settings;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexSettingsRepository;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettings;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsPing;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsRetrieval;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /index/settings")
+public class List_GET extends WebIntegrationTest {
+
+    @Autowired
+    private IndexSettingsRepository indexSettingsRepository;
+
+    private final ParameterizedTypeReference<IndexSettingsDTO> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/index/settings");
+    }
+
+    private IndexSettings customSettings() {
+        return new IndexSettings()
+                .toBuilder()
+                .ping(
+                        new IndexSettingsPing()
+                                .toBuilder()
+                                .denyList(Collections.singletonList("http://localhost.*$"))
+                                .rateLimitDuration(Duration.ofMinutes(17))
+                                .validDuration(Duration.ofDays(5))
+                                .rateLimitHits(666)
+                                .build()
+                )
+                .retrieval(
+                        new IndexSettingsRetrieval()
+                                .toBuilder()
+                                .rateLimitWait(Duration.ofHours(16))
+                                .timeout(Duration.ofSeconds(55))
+                                .build()
+                )
+                .build();
+    }
+
+    @Test
+    @DisplayName("HTTP 200: default settings")
+    public void res200_defaultSettings() {
+        // GIVEN: prepare data
+        IndexSettings settings = IndexSettings.getDefault();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("No settings are created", indexSettingsRepository.findAll().size(), is(equalTo(0)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default valid duration", Objects.requireNonNull(result.getBody()).getPing().getValidDuration(), is(equalTo(settings.getPing().getValidDuration().toString())));
+        assertThat("Response contains default rate limit duration", Objects.requireNonNull(result.getBody()).getPing().getRateLimitDuration(), is(equalTo(settings.getPing().getRateLimitDuration().toString())));
+        assertThat("Response contains default rate limit hits", Objects.requireNonNull(result.getBody()).getPing().getRateLimitHits(), is(equalTo(settings.getPing().getRateLimitHits())));
+        assertThat("Response contains default deny list", Objects.requireNonNull(result.getBody()).getPing().getDenyList(), is(equalTo(settings.getPing().getDenyList())));
+        assertThat("Response contains default timeout", Objects.requireNonNull(result.getBody()).getRetrieval().getTimeout(), is(equalTo(settings.getRetrieval().getTimeout().toString())));
+        assertThat("Response contains default rate limit wait", Objects.requireNonNull(result.getBody()).getRetrieval().getRateLimitWait(), is(equalTo(settings.getRetrieval().getRateLimitWait().toString())));
+        assertThat("Response indicated default settings", Objects.requireNonNull(result.getBody()).getIsDefault(), is(Boolean.TRUE));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: custom settings")
+    public void res200_customSettings() {
+        // GIVEN: prepare data
+        IndexSettings settings = customSettings();
+        indexSettingsRepository.deleteAll();
+        indexSettingsRepository.insert(settings);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("No settings are created", indexSettingsRepository.findAll().size(), is(equalTo(1)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains custom valid duration", Objects.requireNonNull(result.getBody()).getPing().getValidDuration(), is(equalTo(settings.getPing().getValidDuration().toString())));
+        assertThat("Response contains custom rate limit duration", Objects.requireNonNull(result.getBody()).getPing().getRateLimitDuration(), is(equalTo(settings.getPing().getRateLimitDuration().toString())));
+        assertThat("Response contains custom rate limit hits", Objects.requireNonNull(result.getBody()).getPing().getRateLimitHits(), is(equalTo(settings.getPing().getRateLimitHits())));
+        assertThat("Response contains custom deny list", Objects.requireNonNull(result.getBody()).getPing().getDenyList(), is(equalTo(settings.getPing().getDenyList())));
+        assertThat("Response contains custom timeout", Objects.requireNonNull(result.getBody()).getRetrieval().getTimeout(), is(equalTo(settings.getRetrieval().getTimeout().toString())));
+        assertThat("Response contains custom rate limit wait", Objects.requireNonNull(result.getBody()).getRetrieval().getRateLimitWait(), is(equalTo(settings.getRetrieval().getRateLimitWait().toString())));
+        assertThat("Response indicated non-default settings", Objects.requireNonNull(result.getBody()).getIsDefault(), is(Boolean.FALSE));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden without auth", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: not admin")
+    public void res403_notAdmin() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.AUTHORIZATION, NIKOLA_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden for non-admin users", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..b00ce22559d01f7ee20586c62245d304bb384a89
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/index/settings/List_PUT.java
@@ -0,0 +1,273 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.index.settings;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsPingDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsRetrievalDTO;
+import nl.dtls.fairdatapoint.api.dto.index.settings.IndexSettingsUpdateDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.IndexSettingsRepository;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettings;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsPing;
+import nl.dtls.fairdatapoint.entity.index.settings.IndexSettingsRetrieval;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("PUT /index/settings")
+public class List_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private IndexSettingsRepository indexSettingsRepository;
+
+    private final ParameterizedTypeReference<IndexSettingsDTO> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/index/settings");
+    }
+
+    private IndexSettings customSettings1() {
+        return new IndexSettings()
+                .toBuilder()
+                .ping(
+                        new IndexSettingsPing()
+                                .toBuilder()
+                                .denyList(Collections.singletonList("http://localhost.*$"))
+                                .rateLimitDuration(Duration.ofMinutes(17))
+                                .validDuration(Duration.ofDays(5))
+                                .rateLimitHits(666)
+                                .build()
+                )
+                .retrieval(
+                        new IndexSettingsRetrieval()
+                                .toBuilder()
+                                .rateLimitWait(Duration.ofHours(16))
+                                .timeout(Duration.ofSeconds(55))
+                                .build()
+                )
+                .build();
+    }
+
+    private IndexSettings customSettings2() {
+        IndexSettings settings = customSettings1();
+        settings.getPing().setValidDuration(Duration.ofDays(14));
+        settings.getRetrieval().setTimeout(Duration.ofMinutes(2));
+        return settings;
+    }
+
+    private IndexSettingsUpdateDTO customSettingsUpdateDTO() {
+        IndexSettings customSettings = customSettings1();
+        IndexSettingsUpdateDTO dto = new IndexSettingsUpdateDTO();
+        IndexSettingsPingDTO pingDTO = new IndexSettingsPingDTO();
+        pingDTO.setDenyList(customSettings.getPing().getDenyList());
+        pingDTO.setRateLimitDuration(customSettings.getPing().getRateLimitDuration().toString());
+        pingDTO.setValidDuration(customSettings.getPing().getValidDuration().toString());
+        pingDTO.setRateLimitHits(customSettings.getPing().getRateLimitHits());
+        dto.setPing(pingDTO);
+        IndexSettingsRetrievalDTO retrievalDTO = new IndexSettingsRetrievalDTO();
+        retrievalDTO.setRateLimitWait(customSettings.getRetrieval().getRateLimitWait().toString());
+        retrievalDTO.setTimeout(customSettings.getRetrieval().getTimeout().toString());
+        dto.setRetrieval(retrievalDTO);
+        return dto;
+    }
+
+    private IndexSettingsUpdateDTO invalidUpdateDTO1() {
+        IndexSettingsUpdateDTO dto = customSettingsUpdateDTO();
+        dto.getPing().setValidDuration("666");
+        return dto;
+    }
+
+    private IndexSettingsUpdateDTO invalidUpdateDTO2() {
+        IndexSettingsUpdateDTO dto = customSettingsUpdateDTO();
+        dto.getPing().setDenyList(null);
+        return dto;
+    }
+
+    @Test
+    @DisplayName("HTTP 200: update settings from defaults")
+    public void res200_updateSettingsFromDefaults() {
+        // GIVEN: prepare data
+        IndexSettings settings = customSettings1();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(customSettingsUpdateDTO());
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Settings are created", indexSettingsRepository.findAll().size(), is(equalTo(1)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default valid duration", Objects.requireNonNull(result.getBody()).getPing().getValidDuration(), is(equalTo(settings.getPing().getValidDuration().toString())));
+        assertThat("Response contains default rate limit duration", Objects.requireNonNull(result.getBody()).getPing().getRateLimitDuration(), is(equalTo(settings.getPing().getRateLimitDuration().toString())));
+        assertThat("Response contains default rate limit hits", Objects.requireNonNull(result.getBody()).getPing().getRateLimitHits(), is(equalTo(settings.getPing().getRateLimitHits())));
+        assertThat("Response contains default deny list", Objects.requireNonNull(result.getBody()).getPing().getDenyList(), is(equalTo(settings.getPing().getDenyList())));
+        assertThat("Response contains default timeout", Objects.requireNonNull(result.getBody()).getRetrieval().getTimeout(), is(equalTo(settings.getRetrieval().getTimeout().toString())));
+        assertThat("Response contains default rate limit wait", Objects.requireNonNull(result.getBody()).getRetrieval().getRateLimitWait(), is(equalTo(settings.getRetrieval().getRateLimitWait().toString())));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: update settings from custom")
+    public void res200_updateSettingsFromCustom() {
+        // GIVEN: prepare data
+        IndexSettingsUpdateDTO reqDTO = customSettingsUpdateDTO();
+        IndexSettings settings = customSettings1();
+        indexSettingsRepository.deleteAll();
+        indexSettingsRepository.insert(customSettings2());
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Settings are created", indexSettingsRepository.findAll().size(), is(equalTo(1)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default valid duration", Objects.requireNonNull(result.getBody()).getPing().getValidDuration(), is(equalTo(settings.getPing().getValidDuration().toString())));
+        assertThat("Response contains default rate limit duration", Objects.requireNonNull(result.getBody()).getPing().getRateLimitDuration(), is(equalTo(settings.getPing().getRateLimitDuration().toString())));
+        assertThat("Response contains default rate limit hits", Objects.requireNonNull(result.getBody()).getPing().getRateLimitHits(), is(equalTo(settings.getPing().getRateLimitHits())));
+        assertThat("Response contains default deny list", Objects.requireNonNull(result.getBody()).getPing().getDenyList(), is(equalTo(settings.getPing().getDenyList())));
+        assertThat("Response contains default timeout", Objects.requireNonNull(result.getBody()).getRetrieval().getTimeout(), is(equalTo(settings.getRetrieval().getTimeout().toString())));
+        assertThat("Response contains default rate limit wait", Objects.requireNonNull(result.getBody()).getRetrieval().getRateLimitWait(), is(equalTo(settings.getRetrieval().getRateLimitWait().toString())));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: invalid duration format")
+    public void res400_invalidDurationFormat() {
+        // GIVEN: prepare data
+        IndexSettingsUpdateDTO reqDTO = invalidUpdateDTO1();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It indicates bad request", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat("No settings are created", indexSettingsRepository.findAll().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: invalid list")
+    public void res400_invalidList() {
+        // GIVEN: prepare data
+        IndexSettingsUpdateDTO reqDTO = invalidUpdateDTO2();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It indicates bad request", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat("No settings are created", indexSettingsRepository.findAll().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN: prepare data
+        IndexSettingsUpdateDTO reqDTO = customSettingsUpdateDTO();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden without auth", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat("No settings are created", indexSettingsRepository.findAll().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: not admin")
+    public void res403_notAdmin() {
+        // GIVEN: prepare data
+        IndexSettingsUpdateDTO reqDTO = customSettingsUpdateDTO();
+        indexSettingsRepository.deleteAll();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, NIKOLA_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<IndexSettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden for non-admin users", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat("No settings are created", indexSettingsRepository.findAll().size(), is(equalTo(0)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/Common.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/Common.java
index b9a835a1288f907dc4c9c9fc193c2f36c8883212..a9f0deea5ec053cf727494c60a482342dfd529e5 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/Common.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/Common.java
@@ -22,9 +22,23 @@
  */
 package nl.dtls.fairdatapoint.acceptance.metadata;
 
+import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
 import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
 
+import java.net.URI;
+
+import static nl.dtls.fairdatapoint.WebIntegrationTest.ALBERT_TOKEN;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 
@@ -37,4 +51,45 @@ public class Common {
         // Assert
         assertThat(dto, is(equalTo(expDto)));
     }
+
+    public static void assertEmptyState(MetaStateDTO dto) {
+        assertThat(dto, is(nullValue()));
+    }
+
+    public static void createMetadataStateAlreadyPublished(TestRestTemplate client, URI url) {
+        // GIVEN:
+        RequestEntity<MetaStateChangeDTO> request = RequestEntity
+                .put(url)
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .body(new MetaStateChangeDTO(MetadataState.PUBLISHED));
+        ParameterizedTypeReference<ErrorDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ErrorDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat(result.getBody().getMessage(), is(equalTo("Metadata is already published")));
+    }
+
+    public static void createMetadataStateChangeToDraft(TestRestTemplate client, URI url) {
+        // GIVEN:
+        RequestEntity<MetaStateChangeDTO> request = RequestEntity
+                .put(url)
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .body(new MetaStateChangeDTO(MetadataState.DRAFT));
+        ParameterizedTypeReference<ErrorDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ErrorDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat(result.getBody().getMessage(), is(equalTo("You can not change state to DRAFT")));
+    }
+
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Expanded_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Expanded_GET.java
index fa2d463e173e1738dc10df6a00d661c21c0546be..6d06dcd5c2d14c4c86ee74418bd116468050b25b 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Expanded_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Expanded_GET.java
@@ -47,7 +47,7 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
@@ -64,6 +64,44 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
     }
 
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
     @Test
     @DisplayName("HTTP 404")
     public void res404() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_GET.java
index 08cd61544a21d242285afc083506ab88e222095d..1a402a30d9140b670558aa17b1f57ab6b13157b2 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_GET.java
@@ -47,7 +47,7 @@ public class Detail_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
@@ -64,6 +64,44 @@ public class Detail_GET extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
     }
 
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
     @Test
     @DisplayName("HTTP 404")
     public void res404() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_PUT.java
index 6f4eac932e91b1613b1b55319d3aa8c5835aca3d..0d7c8c749b0948003d5bfae2f8255260069bf4fb 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_PUT.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_PUT.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.acceptance.metadata.catalog;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.rio.RDFFormat;
@@ -55,7 +55,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class Detail_PUT extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     private URI url(String id) {
         return URI.create(format("/catalog/%s", id));
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Page_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Page_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..be6da9dd2e3f6320b71cd490fa7848ebbee5b50a
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Page_GET.java
@@ -0,0 +1,111 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.catalog;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createUserNotFoundTestGetRDF;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /catalog/:catalogId/page/dataset")
+public class Detail_Page_GET extends WebIntegrationTest {
+
+    private URI url(String id) {
+        return URI.create(format("/catalog/%s/page/dataset", id));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: Published")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-1"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("catalog-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createUserNotFoundTestGetRDF(client, url("nonExisting"));
+    }
+}
+
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/List_POST.java
index c58bff1cc6bfb4db75df2b62c41b8077d6fd4528..73ae3f2b77b0f40d44a51e3e08fca6775440764d 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/List_POST.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/List_POST.java
@@ -23,9 +23,9 @@
 package nl.dtls.fairdatapoint.acceptance.metadata.catalog;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
-import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.MetadataMigration;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.RdfMetadataMigration;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.rio.RDFFormat;
 import org.junit.jupiter.api.DisplayName;
@@ -50,7 +50,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class List_POST extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     @Autowired
     private AclRepository aclRepository;
@@ -59,7 +59,7 @@ public class List_POST extends WebIntegrationTest {
     private AclCache aclCache;
 
     @Autowired
-    private MetadataMigration metadataMigration;
+    private RdfMetadataMigration rdfMetadataMigration;
 
     private URI url() {
         return URI.create("/catalog");
@@ -89,36 +89,6 @@ public class List_POST extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.CREATED)));
     }
 
-    @Test
-    @DisplayName("HTTP 201 (with rerouting)")
-    public void res201_withRerouting() throws Exception {
-        // GIVEN: We need to clear all permissions from default FDP fixtures
-        aclRepository.deleteAll();
-        aclCache.clearCache();
-        // AND: Prepare fixtures
-        metadataMigration.importDefaultFixtures(testMetadataFixtures.alternativePersistentUrl);
-        Model catalog3 = testMetadataFixtures.alternative_catalog3();
-        String reqDto = RdfIOUtil.write(catalog3, RDFFormat.TURTLE);
-        // AND: Prepare request
-        RequestEntity<String> request = RequestEntity
-                .post(url())
-                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
-                .header(HttpHeaders.CONTENT_TYPE, "text/turtle")
-                .header(HttpHeaders.ACCEPT, "text/turtle")
-                .header("x-forwarded-host", "lorentz.fair-dtls.surf-hosted.nl")
-                .header("x-forwarded-proto", "https")
-                .header("x-forwarded-port", "443")
-                .body(reqDto);
-        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
-        };
-
-        // WHEN:
-        ResponseEntity<String> result = client.exchange(request, responseType);
-
-        // THEN:
-        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.CREATED)));
-    }
-
     @Test
     @DisplayName("HTTP 403: Anonymous access")
     public void res403_anonymous() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Member_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/meta/List_GET.java
old mode 100755
new mode 100644
similarity index 63%
rename from src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Member_GET.java
rename to src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/meta/List_GET.java
index 934edacbb01cbfedd5890fc973ecd1e720d40978..7e6bf8f4c859fd7962849f8d3846919de02ab118
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/Detail_Member_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/meta/List_GET.java
@@ -20,11 +20,14 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-package nl.dtls.fairdatapoint.acceptance.metadata.catalog;
+package nl.dtls.fairdatapoint.acceptance.metadata.catalog.meta;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.membership.data.MembershipFixtures;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
 import nl.dtls.fairdatapoint.service.member.MemberMapper;
 import org.junit.jupiter.api.DisplayName;
@@ -37,15 +40,18 @@ import org.springframework.http.RequestEntity;
 import org.springframework.http.ResponseEntity;
 
 import java.net.URI;
+import java.util.Map;
 
 import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createUserNotFoundTestGetRDF;
 import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyMember;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyState;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 
-@DisplayName("GET /catalog/:catalogId/member")
-public class Detail_Member_GET extends WebIntegrationTest {
+@DisplayName("GET /catalog/:catalogId/meta")
+public class List_GET extends WebIntegrationTest {
 
     @Autowired
     private UserFixtures userFixtures;
@@ -56,8 +62,11 @@ public class Detail_Member_GET extends WebIntegrationTest {
     @Autowired
     private MemberMapper memberMapper;
 
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
     private URI url(String id) {
-        return URI.create(format("/catalog/%s/member", id));
+        return URI.create(format("/catalog/%s/meta", id));
     }
 
     @Test
@@ -69,18 +78,25 @@ public class Detail_Member_GET extends WebIntegrationTest {
                 .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
                 .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
         // AND: prepare expectation
-        MemberDTO expDto = memberMapper.toDTO(userFixtures.albert(), membershipFixtures.owner());
+        MemberDTO expMember = memberMapper.toDTO(userFixtures.albert(), membershipFixtures.owner());
 
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertThat(result.getBody(), is(equalTo(expDto)));
+        assertThat(result.getBody().getMember(), is(equalTo(expMember)));
+        assertThat(result.getBody().getState(), is(equalTo(new MetaStateDTO(
+                metadataFixtures.catalog1().getState(),
+                Map.of(
+                        metadataFixtures.dataset1().getUri(), metadataFixtures.dataset1().getState(),
+                        metadataFixtures.dataset2().getUri(), metadataFixtures.dataset2().getState()
+                )
+        ))));
     }
 
     @Test
@@ -91,15 +107,22 @@ public class Detail_Member_GET extends WebIntegrationTest {
                 .get(url("catalog-1"))
                 .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertEmptyMember(result.getBody());
+        assertEmptyMember(result.getBody().getMember());
+        assertEmptyState(result.getBody().getState());
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createUserNotFoundTestGetRDF(client, url("nonExisting"));
     }
 
 
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/meta/List_State_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/meta/List_State_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d58754b06f5fec0fbe5bc277fa449462a8291ce
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/catalog/meta/List_State_PUT.java
@@ -0,0 +1,110 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.catalog.meta;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateAlreadyPublished;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateChangeToDraft;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /catalog/:catalogId/meta/state")
+public class List_State_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    private URI url(String id) {
+        return URI.create(format("/catalog/%s/meta/state", id));
+    }
+
+    private MetaStateChangeDTO reqDto() {
+        return new MetaStateChangeDTO(MetadataState.PUBLISHED);
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<MetaStateChangeDTO> request = RequestEntity
+                .put(url("catalog-1"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .body(reqDto());
+        ParameterizedTypeReference<MetaStateChangeDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // AND: Prepare database
+        Metadata metadata = metadataRepository.findByUri(metadataFixtures.catalog1().getUri()).get();
+        metadata.setState(MetadataState.DRAFT);
+        metadataRepository.save(metadata);
+
+        // WHEN:
+        ResponseEntity<MetaStateChangeDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(equalTo(reqDto())));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: Metadata is already published")
+    public void res400_already_published() {
+        createMetadataStateAlreadyPublished(client, url("catalog-1"));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: You can not change state to DRAFT")
+    public void res400_change_to_draft() {
+        createMetadataStateChangeToDraft(client, url("catalog-1"));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPut(client, url("catalog-1"), reqDto());
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Expanded_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Expanded_GET.java
index dcbe52b2afb82e8b4f5522f2b2d8e530d270a465..03c2ec0f84ac005d607e7ca135ec5230d94edd26 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Expanded_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Expanded_GET.java
@@ -47,7 +47,7 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
@@ -64,6 +64,44 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
     }
 
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
     @Test
     @DisplayName("HTTP 404")
     public void res404() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_GET.java
index 48a815a6d4383184ab11c383a2621c63d2355a1d..97129b1b53668dea07ee1771c5af2c4f82e522b6 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_GET.java
@@ -47,7 +47,7 @@ public class Detail_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
@@ -64,6 +64,44 @@ public class Detail_GET extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
     }
 
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
     @Test
     @DisplayName("HTTP 404")
     public void res404() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_PUT.java
index 53f58040705b25d4964458cc3e8e3afe12ebc4f0..da9c08ef4a17bac12d233a6e8c4272d28964d42d 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_PUT.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_PUT.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.acceptance.metadata.dataset;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.rio.RDFFormat;
@@ -55,7 +55,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class Detail_PUT extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     private URI url(String id) {
         return URI.create(format("/dataset/%s", id));
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Page_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Page_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..e61e0ce393a488918c21e6197e610e3914b6943e
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Page_GET.java
@@ -0,0 +1,110 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.dataset;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createUserNotFoundTestGetRDF;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /dataset/:datasetId/page/distribution")
+public class Detail_Page_GET extends WebIntegrationTest {
+
+    private URI url(String id) {
+        return URI.create(format("/dataset/%s/page/distribution", id));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: Published")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-1"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createUserNotFoundTestGetRDF(client, url("nonExisting"));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/List_POST.java
index f9fc3434b58cbcc94395c658383b2073a47f88d1..9d0ce47f6ff3ccd550c1b0199ba3fdb1549e9aaf 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/List_POST.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/List_POST.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.acceptance.metadata.dataset;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.rio.RDFFormat;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -46,7 +46,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class List_POST extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     private URI url() {
         return URI.create("/dataset");
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/meta/List_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/meta/List_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..e79d05da0787a36e7807f88cd282bd6444f351c8
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/meta/List_GET.java
@@ -0,0 +1,128 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.dataset.meta;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.membership.data.MembershipFixtures;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
+import nl.dtls.fairdatapoint.service.member.MemberMapper;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.Map;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createUserNotFoundTestGetRDF;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyMember;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyState;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /dataset/:datasetId/meta")
+public class List_GET extends WebIntegrationTest {
+
+    @Autowired
+    private UserFixtures userFixtures;
+
+    @Autowired
+    private MembershipFixtures membershipFixtures;
+
+    @Autowired
+    private MemberMapper memberMapper;
+
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
+    private URI url(String id) {
+        return URI.create(format("/dataset/%s/meta", id));
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-1"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .build();
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // AND: prepare expectation
+        MemberDTO expMember = memberMapper.toDTO(userFixtures.albert(), membershipFixtures.owner());
+
+        // WHEN:
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody().getMember(), is(equalTo(expMember)));
+        assertThat(result.getBody().getState(), is(equalTo(new MetaStateDTO(
+                metadataFixtures.dataset1().getState(),
+                Map.of(
+                        metadataFixtures.distribution1().getUri(), metadataFixtures.distribution1().getState(),
+                        metadataFixtures.distribution2().getUri(), metadataFixtures.distribution2().getState()
+                )
+        ))));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: No user")
+    public void res200_no_user() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("dataset-1"))
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .build();
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertEmptyMember(result.getBody().getMember());
+        assertEmptyState(result.getBody().getState());
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createUserNotFoundTestGetRDF(client, url("nonExisting"));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/meta/List_State_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/meta/List_State_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a958a4bb805c8623320e9268944747dc7690f63
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/meta/List_State_PUT.java
@@ -0,0 +1,110 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.dataset.meta;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateAlreadyPublished;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateChangeToDraft;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /dataset/:datasetId/meta/state")
+public class List_State_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    private URI url(String id) {
+        return URI.create(format("/dataset/%s/meta/state", id));
+    }
+
+    private MetaStateChangeDTO reqDto() {
+        return new MetaStateChangeDTO(MetadataState.PUBLISHED);
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<MetaStateChangeDTO> request = RequestEntity
+                .put(url("dataset-1"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .body(reqDto());
+        ParameterizedTypeReference<MetaStateChangeDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // AND: Prepare database
+        Metadata metadata = metadataRepository.findByUri(metadataFixtures.dataset1().getUri()).get();
+        metadata.setState(MetadataState.DRAFT);
+        metadataRepository.save(metadata);
+
+        // WHEN:
+        ResponseEntity<MetaStateChangeDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(equalTo(reqDto())));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: Metadata is already published")
+    public void res400_already_published() {
+        createMetadataStateAlreadyPublished(client, url("dataset-1"));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: You can not change state to DRAFT")
+    public void res400_change_to_draft() {
+        createMetadataStateChangeToDraft(client, url("dataset-1"));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPut(client, url("dataset-1"), reqDto());
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Expanded_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Expanded_GET.java
index afa45b43f7585f5e124ab9919f8094a5595f6d42..6c9471bdeebc403c66e185b3181cf2e1bef5d787 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Expanded_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Expanded_GET.java
@@ -47,7 +47,7 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
@@ -64,6 +64,44 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
     }
 
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("distribution-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("distribution-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
     @Test
     @DisplayName("HTTP 404")
     public void res404() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_GET.java
index 8f2129af0fe80c8ef1e7e4eb098cd9f15ba1434c..3057134f51b8c53e3d0bc1bf5b859f278215c215 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_GET.java
@@ -47,7 +47,7 @@ public class Detail_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
@@ -64,6 +64,44 @@ public class Detail_GET extends WebIntegrationTest {
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
     }
 
+    @Test
+    @DisplayName("HTTP 200: Draft (User is logged in)")
+    public void res200_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("distribution-2"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: Draft (User is not logged in)")
+    public void res403_draft() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url("distribution-2"))
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat(result.getBody(), is(equalTo("You are not allow to view this record in state DRAFT")));
+    }
+
     @Test
     @DisplayName("HTTP 404")
     public void res404() {
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_PUT.java
index 87367d73708ae748dfe05f11fbd898044c7da995..138c1ab050df9b3d5fbe39471d03c4274297b227 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_PUT.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_PUT.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.acceptance.metadata.distribution;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.rio.RDFFormat;
@@ -54,7 +54,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class Detail_PUT extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     private URI url(String id) {
         return URI.create(format("/distribution/%s", id));
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/List_POST.java
index a4497e26d46222bfe12ff3e4af6cb25f5d35393a..52f9b8c1b054312b5afe539116ca6d539b276b5f 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/List_POST.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/List_POST.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.acceptance.metadata.distribution;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.rio.RDFFormat;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -46,7 +46,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class List_POST extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     private URI url() {
         return URI.create("/distribution");
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Member_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/meta/List_GET.java
old mode 100755
new mode 100644
similarity index 66%
rename from src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Member_GET.java
rename to src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/meta/List_GET.java
index 7ae8cceb7793f82ce529a7ab6e53c25cdafcfa64..1186c91acb32d3dc9f8ceab7975c2d5919faafff
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/Detail_Member_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/meta/List_GET.java
@@ -20,11 +20,14 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-package nl.dtls.fairdatapoint.acceptance.metadata.distribution;
+package nl.dtls.fairdatapoint.acceptance.metadata.distribution.meta;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.membership.data.MembershipFixtures;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
 import nl.dtls.fairdatapoint.service.member.MemberMapper;
 import org.junit.jupiter.api.DisplayName;
@@ -37,15 +40,18 @@ import org.springframework.http.RequestEntity;
 import org.springframework.http.ResponseEntity;
 
 import java.net.URI;
+import java.util.Map;
 
 import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createUserNotFoundTestGetRDF;
 import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyMember;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyState;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 
-@DisplayName("GET /distribution/:distributionId/member")
-public class Detail_Member_GET extends WebIntegrationTest {
+@DisplayName("GET /distribution/:distributionId/meta")
+public class List_GET extends WebIntegrationTest {
 
     @Autowired
     private UserFixtures userFixtures;
@@ -56,8 +62,11 @@ public class Detail_Member_GET extends WebIntegrationTest {
     @Autowired
     private MemberMapper memberMapper;
 
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
     private URI url(String id) {
-        return URI.create(format("/distribution/%s/member", id));
+        return URI.create(format("/distribution/%s/meta", id));
     }
 
     @Test
@@ -69,18 +78,22 @@ public class Detail_Member_GET extends WebIntegrationTest {
                 .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
                 .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
         // AND: prepare expectation
-        MemberDTO expDto = memberMapper.toDTO(userFixtures.albert(), membershipFixtures.owner());
+        MemberDTO expMember = memberMapper.toDTO(userFixtures.albert(), membershipFixtures.owner());
 
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertThat(result.getBody(), is(equalTo(expDto)));
+        assertThat(result.getBody().getMember(), is(equalTo(expMember)));
+        assertThat(result.getBody().getState(), is(equalTo(new MetaStateDTO(
+                metadataFixtures.distribution1().getState(),
+                Map.of()
+        ))));
     }
 
     @Test
@@ -91,15 +104,22 @@ public class Detail_Member_GET extends WebIntegrationTest {
                 .get(url("distribution-1"))
                 .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertEmptyMember(result.getBody());
+        assertEmptyMember(result.getBody().getMember());
+        assertEmptyState(result.getBody().getState());
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createUserNotFoundTestGetRDF(client, url("nonExisting"));
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/meta/List_State_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/meta/List_State_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bd2748a2e702f1290d637e7bc809a16d288c357
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/distribution/meta/List_State_PUT.java
@@ -0,0 +1,110 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.distribution.meta;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateAlreadyPublished;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateChangeToDraft;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /distribution/:distributionId/meta/state")
+public class List_State_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    private URI url(String id) {
+        return URI.create(format("/distribution/%s/meta/state", id));
+    }
+
+    private MetaStateChangeDTO reqDto() {
+        return new MetaStateChangeDTO(MetadataState.PUBLISHED);
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<MetaStateChangeDTO> request = RequestEntity
+                .put(url("distribution-1"))
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .body(reqDto());
+        ParameterizedTypeReference<MetaStateChangeDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // AND: Prepare database
+        Metadata metadata = metadataRepository.findByUri(metadataFixtures.distribution1().getUri()).get();
+        metadata.setState(MetadataState.DRAFT);
+        metadataRepository.save(metadata);
+
+        // WHEN:
+        ResponseEntity<MetaStateChangeDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(equalTo(reqDto())));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: Metadata is already published")
+    public void res400_already_published() {
+        createMetadataStateAlreadyPublished(client, url("distribution-1"));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: You can not change state to DRAFT")
+    public void res400_change_to_draft() {
+        createMetadataStateChangeToDraft(client, url("distribution-1"));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPut(client, url("distribution-1"), reqDto());
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Expanded_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Expanded_GET.java
index a9e39b0af93738c0f541e59cecc0b7ba327d96b0..57a7fbe0adc95df1e4d6d207ab3eb8887db9daa3 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Expanded_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Expanded_GET.java
@@ -45,7 +45,7 @@ public class Detail_Expanded_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_GET.java
index 5e5f91a5717287409e934a26f9aed06e6ed34df9..b9a4a3b22627d1f69b54a81aecc6d8854c054ceb 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_GET.java
@@ -45,7 +45,7 @@ public class Detail_GET extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 200")
+    @DisplayName("HTTP 200: Published")
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_PUT.java
index 06ae21b1c75143f0703020b0971dd728eabb4261..beeb8586378bbaaa40b5c71f1518bf894ac5799f 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_PUT.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_PUT.java
@@ -24,7 +24,7 @@ package nl.dtls.fairdatapoint.acceptance.metadata.repository;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
 import nl.dtls.fairdatapoint.util.RdfIOUtil;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.rio.RDFFormat;
@@ -52,7 +52,7 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class Detail_PUT extends WebIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     private URI url() {
         return URI.create("/");
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Page_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Page_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2bf9e90be93cdd141659e34705cbb08b471ebab
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/Detail_Page_GET.java
@@ -0,0 +1,62 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.repository;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+public class Detail_Page_GET extends WebIntegrationTest {
+    private URI url() {
+        return URI.create("/page/catalog");
+    }
+
+    @Test
+    @DisplayName("HTTP 200: Published")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.ACCEPT, "text/turtle")
+                .build();
+        ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<String> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Member_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/meta/List_GET.java
old mode 100755
new mode 100644
similarity index 64%
rename from src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Member_GET.java
rename to src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/meta/List_GET.java
index c9c3bbb70917243696f5be5332074c6a69c61757..ece3a1695b49352a13d2c9628a16cf410f378201
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/dataset/Detail_Member_GET.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/meta/List_GET.java
@@ -20,13 +20,12 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-package nl.dtls.fairdatapoint.acceptance.metadata.dataset;
+package nl.dtls.fairdatapoint.acceptance.metadata.repository.meta;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
-import nl.dtls.fairdatapoint.api.dto.member.MemberDTO;
-import nl.dtls.fairdatapoint.database.mongo.migration.development.membership.data.MembershipFixtures;
-import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
-import nl.dtls.fairdatapoint.service.member.MemberMapper;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaDTO;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -37,27 +36,22 @@ import org.springframework.http.RequestEntity;
 import org.springframework.http.ResponseEntity;
 
 import java.net.URI;
+import java.util.Map;
 
-import static java.lang.String.format;
 import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyMember;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.assertEmptyState;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 
-@DisplayName("GET /dataset/:datasetId/member")
-public class Detail_Member_GET extends WebIntegrationTest {
+@DisplayName("GET /meta")
+public class List_GET extends WebIntegrationTest {
 
     @Autowired
-    private UserFixtures userFixtures;
+    private MetadataFixtures metadataFixtures;
 
-    @Autowired
-    private MembershipFixtures membershipFixtures;
-
-    @Autowired
-    private MemberMapper memberMapper;
-
-    private URI url(String id) {
-        return URI.create(format("/dataset/%s/member", id));
+    private URI url() {
+        return URI.create("/meta");
     }
 
     @Test
@@ -65,22 +59,26 @@ public class Detail_Member_GET extends WebIntegrationTest {
     public void res200() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
-                .get(url("dataset-1"))
+                .get(url())
                 .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
                 .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
-        // AND: prepare expectation
-        MemberDTO expDto = memberMapper.toDTO(userFixtures.albert(), membershipFixtures.owner());
-
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertThat(result.getBody(), is(equalTo(expDto)));
+        assertEmptyMember(result.getBody().getMember());
+        assertThat(result.getBody().getState(), is(equalTo(new MetaStateDTO(
+                metadataFixtures.repositoryMetadata().getState(),
+                Map.of(
+                        metadataFixtures.catalog1().getUri(), metadataFixtures.catalog1().getState(),
+                        metadataFixtures.catalog2().getUri(), metadataFixtures.catalog2().getState()
+                )
+        ))));
     }
 
     @Test
@@ -88,18 +86,19 @@ public class Detail_Member_GET extends WebIntegrationTest {
     public void res200_no_user() {
         // GIVEN:
         RequestEntity<Void> request = RequestEntity
-                .get(url("dataset-1"))
+                .get(url())
                 .header(HttpHeaders.ACCEPT, "application/json")
                 .build();
-        ParameterizedTypeReference<MemberDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<MetaDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
         // WHEN:
-        ResponseEntity<MemberDTO> result = client.exchange(request, responseType);
+        ResponseEntity<MetaDTO> result = client.exchange(request, responseType);
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
-        assertEmptyMember(result.getBody());
+        assertEmptyMember(result.getBody().getMember());
+        assertEmptyState(result.getBody().getState());
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/meta/List_State_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/meta/List_State_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..a35767a03e6a38d2c55490d874550401f4f639e9
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/metadata/repository/meta/List_State_PUT.java
@@ -0,0 +1,109 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.metadata.repository.meta;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.metadata.MetaStateChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.MetadataRepository;
+import nl.dtls.fairdatapoint.entity.metadata.Metadata;
+import nl.dtls.fairdatapoint.entity.metadata.MetadataState;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateAlreadyPublished;
+import static nl.dtls.fairdatapoint.acceptance.metadata.Common.createMetadataStateChangeToDraft;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /meta/state")
+public class List_State_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private MetadataFixtures metadataFixtures;
+
+    @Autowired
+    private MetadataRepository metadataRepository;
+
+    private URI url() {
+        return URI.create("/meta/state");
+    }
+
+    private MetaStateChangeDTO reqDto() {
+        return new MetaStateChangeDTO(MetadataState.PUBLISHED);
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<MetaStateChangeDTO> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .header(HttpHeaders.ACCEPT, "application/json")
+                .body(reqDto());
+        ParameterizedTypeReference<MetaStateChangeDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // AND: Prepare database
+        Metadata metadata = metadataRepository.findByUri(metadataFixtures.repositoryMetadata().getUri()).get();
+        metadata.setState(MetadataState.DRAFT);
+        metadataRepository.save(metadata);
+
+        // WHEN:
+        ResponseEntity<MetaStateChangeDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(equalTo(reqDto())));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: Metadata is already published")
+    public void res400_already_published() {
+        createMetadataStateAlreadyPublished(client, url());
+    }
+
+    @Test
+    @DisplayName("HTTP 400: You can not change state to DRAFT")
+    public void res400_change_to_draft() {
+        createMetadataStateChangeToDraft(client, url());
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPut(client, url(), reqDto());
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/openapi/OpenApiDocs_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/openapi/OpenApiDocs_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..220f80049877c1f28b57e3947f6de4e30b01614d
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/openapi/OpenApiDocs_GET.java
@@ -0,0 +1,63 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.openapi;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /v3/api-docs")
+public class OpenApiDocs_GET extends WebIntegrationTest {
+
+    private URI url() {
+        return URI.create("/v3/api-docs");
+    }
+
+    @Test
+    @DisplayName("HTTP 200: API Docs")
+    public void res200_apiDocs() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<String> result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+        // THEN
+        assertThat("Response code is OK", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response is JSON", result.getHeaders().getContentType(), is(equalTo(MediaType.APPLICATION_JSON)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/openapi/SwaggerUI_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/openapi/SwaggerUI_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a3791ce4fc9d4b4ba86b78e195a908e7de7778a
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/openapi/SwaggerUI_GET.java
@@ -0,0 +1,88 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.openapi;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.index.entry.IndexEntryDTO;
+import nl.dtls.fairdatapoint.utils.CustomPageImpl;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /swagger-ui.html")
+public class SwaggerUI_GET extends WebIntegrationTest {
+
+    private URI baseUrl() {
+        return URI.create("/swagger-ui.html");
+    }
+
+    private URI redirectedUrl() {
+        return URI.create("/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config");
+    }
+
+    @Test
+    @DisplayName("HTTP 302: Redirects to Swagger UI")
+    public void res302_redirectsToSwaggerUI() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(baseUrl())
+                .accept(MediaType.TEXT_HTML)
+                .build();
+
+        // WHEN
+        ResponseEntity<String> result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+        // THEN
+        assertThat("Response code is FOUND", result.getStatusCode(), is(equalTo(HttpStatus.FOUND)));
+        assertThat("Contains Location header", result.getHeaders().getLocation(), is(notNullValue()));
+        assertThat("Contains correct Location header", result.getHeaders().getLocation().toString().endsWith(redirectedUrl().toString()), is(Boolean.TRUE));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: Swagger UI")
+    public void res200_swaggerUI() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(redirectedUrl())
+                .accept(MediaType.TEXT_HTML)
+                .build();
+
+        // WHEN
+        ResponseEntity<String> result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+        // THEN
+        assertThat("Response code is OK", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response is Swagger UI in HTML", result.getHeaders().getContentType(), is(equalTo(MediaType.TEXT_HTML)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_DELETE.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_DELETE.java
new file mode 100644
index 0000000000000000000000000000000000000000..e97158b2c241197e2a4d233755b2831bb6384a30
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_DELETE.java
@@ -0,0 +1,95 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.resource;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createUserForbiddenTestDelete;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createAdminNotFoundTestDelete;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("DELETE /resource-definitions/:resourceDefinitionUuid")
+public class Detail_DELETE extends WebIntegrationTest {
+
+    private URI url(String uuid) {
+        return URI.create(format("/resource-definitions/%s", uuid));
+    }
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @Test
+    @DisplayName("HTTP 204")
+    public void res204() {
+        // GIVEN:
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.repositoryDefinition();
+        RequestEntity<Void> request = RequestEntity
+                .delete(url(resourceDefinition.getUuid()))
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .build();
+        ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.repositoryDefinition();
+        createUserForbiddenTestDelete(client, url(resourceDefinition.getUuid()));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not an admin")
+    public void res403_resourceDefinition() {
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.repositoryDefinition();
+        createUserForbiddenTestDelete(client, url(resourceDefinition.getUuid()));
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createAdminNotFoundTestDelete(client, url("nonExisting"));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e4686e1966435b5c09af68734870a004fc42181
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_GET.java
@@ -0,0 +1,79 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.resource;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createUserNotFoundTestGet;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /resource-definitions/:resourceDefinitionUuid")
+public class Detail_GET extends WebIntegrationTest {
+
+    private URI url(String uuid) {
+        return URI.create(format("/resource-definitions/%s", uuid));
+    }
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.repositoryDefinition();
+        RequestEntity<Void> request = RequestEntity
+                .get(url(resourceDefinition.getUuid()))
+                .build();
+        ParameterizedTypeReference<ResourceDefinition> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ResourceDefinition> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(equalTo(resourceDefinition)));
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createUserNotFoundTestGet(client, url("nonExisting"));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..576db354ffdb37fa1341aee4542ae518b482956c
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/Detail_PUT.java
@@ -0,0 +1,115 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.resource;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionMapper;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createUserForbiddenTestPut;
+import static nl.dtls.fairdatapoint.acceptance.common.NotFoundTest.createAdminNotFoundTestPut;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /resource-definitions/:resourceDefinitionUuid")
+public class Detail_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private ResourceDefinitionMapper resourceDefinitionMapper;
+
+    private URI url(String uuid) {
+        return URI.create(format("/resource-definitions/%s", uuid));
+    }
+
+    private ResourceDefinitionChangeDTO reqDto(ResourceDefinition rd) {
+        rd.setName(format("EDITED: %s", rd.getName()));
+        rd.setUrlPrefix(format("%s-edited", rd.getName()));
+        return resourceDefinitionMapper.toChangeDTO(rd);
+    }
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN: Prepare data
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.catalogDefinition();
+        ResourceDefinitionChangeDTO reqDto = reqDto(resourceDefinition);
+
+        // AND: Prepare request
+        RequestEntity<ResourceDefinitionChangeDTO> request = RequestEntity
+                .put(url(resourceDefinition.getUuid()))
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+        ParameterizedTypeReference<ResourceDefinition> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ResourceDefinition> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody().getName(), is(equalTo(reqDto.getName())));
+        assertThat(result.getBody().getUrlPrefix(), is(equalTo(reqDto.getUrlPrefix())));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: ResourceDefinition is not authenticated")
+    public void res403_notAuthenticated() {
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.catalogDefinition();
+        createNoUserForbiddenTestPut(client, url(resourceDefinition.getUuid()), reqDto(resourceDefinition));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: ResourceDefinition is not an admin")
+    public void res403_resourceDefinition() {
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.catalogDefinition();
+        createUserForbiddenTestPut(client, url(resourceDefinition.getUuid()), reqDto(resourceDefinition));
+    }
+
+    @Test
+    @DisplayName("HTTP 404")
+    public void res404() {
+        createAdminNotFoundTestPut(client, url("nonExisting"),
+                reqDto(resourceDefinitionFixtures.catalogDefinition()));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/List_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/List_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..36f7b229910ffe0ee860f143a65fde1705a5af0f
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/List_GET.java
@@ -0,0 +1,77 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.resource;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /resource-definitions")
+public class List_GET extends WebIntegrationTest {
+
+    private URI url() {
+        return URI.create("/resource-definitions");
+    }
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<Void> request = RequestEntity
+                .get(url())
+                .build();
+        ParameterizedTypeReference<List<ResourceDefinition>> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<List<ResourceDefinition>> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        List<ResourceDefinition> body = result.getBody();
+        assertThat(body, is(equalTo(List.of(
+                resourceDefinitionFixtures.repositoryDefinition(),
+                resourceDefinitionFixtures.catalogDefinition(),
+                resourceDefinitionFixtures.datasetDefinition(),
+                resourceDefinitionFixtures.distributionDefinition()
+        ))));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/List_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..46dbfb8d32e2bf050c485da4abbcc2a26dbda096
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/resource/List_POST.java
@@ -0,0 +1,97 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.resource;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.resource.ResourceDefinitionChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionMapper;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPost;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createUserForbiddenTestPost;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("POST /resource-definitions")
+public class List_POST extends WebIntegrationTest {
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @Autowired
+    private ResourceDefinitionMapper resourceDefinitionMapper;
+
+    private URI url() {
+        return URI.create("/resource-definitions");
+    }
+
+    private ResourceDefinitionChangeDTO reqDto(ResourceDefinition resourceDefinition) {
+        return resourceDefinitionMapper.toChangeDTO(resourceDefinition);
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN: Prepare data
+        ResourceDefinition resourceDefinition = resourceDefinitionFixtures.ontologyDefinition();
+        ResourceDefinitionChangeDTO reqDto = reqDto(resourceDefinition);
+
+        // AND: Prepare request
+        RequestEntity<ResourceDefinitionChangeDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+        ParameterizedTypeReference<ResourceDefinition> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ResourceDefinition> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody().getName(), is(equalTo(reqDto.getName())));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: ResourceDefinition is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPost(client, url(), reqDto(resourceDefinitionFixtures.ontologyDefinition()));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: ResourceDefinition is not an admin")
+    public void res403_resourceDefinition() {
+        createUserForbiddenTestPost(client, url(), reqDto(resourceDefinitionFixtures.ontologyDefinition()));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/search/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/search/List_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6616f3f0e9b3f469a537271c0e3034cd6d330eb
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/search/List_POST.java
@@ -0,0 +1,74 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.search;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.search.SearchQueryDTO;
+import nl.dtls.fairdatapoint.api.dto.search.SearchResultDTO;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("POST /search")
+public class List_POST extends WebIntegrationTest {
+
+    private URI url() {
+        return URI.create("/search");
+    }
+
+    private SearchQueryDTO reqDto(String query) {
+        return new SearchQueryDTO(query);
+    }
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN: Prepare data
+        SearchQueryDTO reqDto = reqDto("catalog");
+
+        // AND: Prepare request
+        RequestEntity<SearchQueryDTO> request = RequestEntity
+                .post(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+        ParameterizedTypeReference<List<SearchResultDTO>> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<List<SearchResultDTO>> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody().size(), is(equalTo(1)));
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_DELETE.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_DELETE.java
new file mode 100644
index 0000000000000000000000000000000000000000..04d652b2a2b29832ef1f545be759578165aa5f7e
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_DELETE.java
@@ -0,0 +1,177 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.settings;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.entity.settings.SettingsMetricsEntry;
+import nl.dtls.fairdatapoint.entity.settings.SettingsPing;
+import nl.dtls.fairdatapoint.service.settings.SettingsCache;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("DELETE /settings")
+public class List_DELETE extends WebIntegrationTest {
+
+    @Autowired
+    private SettingsRepository settingsRepository;
+
+    @Autowired
+    private SettingsCache settingsCache;
+
+    private final ParameterizedTypeReference<SettingsDTO> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/settings");
+    }
+
+    private Settings customSettings() {
+        return Settings.builder()
+                .metadataMetrics(
+                        List.of(new SettingsMetricsEntry(
+                                "http://example.com/metric",
+                                "http://example.com/resource"
+                        ))
+                )
+                .ping(SettingsPing.builder()
+                        .enabled(false)
+                        .endpoints(List.of(
+                                "https://home.fairdatapoint.org",
+                                "https://example.com/index"
+                        ))
+                        .build()
+                )
+                .build();
+    }
+
+    @Test
+    @DisplayName("HTTP 200: default settings")
+    public void res200_defaultSettings() {
+        // GIVEN: prepare data
+        Settings defaultSettings = Settings.getDefault();
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default metrics", Objects.requireNonNull(result.getBody()).getMetadataMetrics(), is(equalTo(defaultSettings.getMetadataMetrics())));
+        assertThat("Response contains default ping enabled", Objects.requireNonNull(result.getBody()).getPing().getEnabled(), is(equalTo(defaultSettings.getPing().isEnabled())));
+        assertThat("Response contains default ping endpoints", Objects.requireNonNull(result.getBody()).getPing().getEndpoints(), is(equalTo(defaultSettings.getPing().getEndpoints())));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: custom settings")
+    public void res200_customSettings() {
+        // GIVEN: prepare data
+        Settings defaultSettings = Settings.getDefault();
+        Settings customSettings = customSettings();
+        settingsRepository.deleteAll();
+        settingsRepository.insert(customSettings);
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(1)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default metrics", Objects.requireNonNull(result.getBody()).getMetadataMetrics(), is(equalTo(defaultSettings.getMetadataMetrics())));
+        assertThat("Response contains default ping enabled", Objects.requireNonNull(result.getBody()).getPing().getEnabled(), is(equalTo(defaultSettings.getPing().isEnabled())));
+        assertThat("Response contains default ping endpoints", Objects.requireNonNull(result.getBody()).getPing().getEndpoints(), is(equalTo(defaultSettings.getPing().getEndpoints())));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden without auth", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: not admin")
+    public void res403_notAdmin() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .delete(url())
+                .header(HttpHeaders.AUTHORIZATION, NIKOLA_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden for non-admin users", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @AfterEach
+    public void teardown() {
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a54fe6457504201ae106395bc668ea053383989
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_GET.java
@@ -0,0 +1,178 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.settings;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.entity.settings.SettingsMetricsEntry;
+import nl.dtls.fairdatapoint.entity.settings.SettingsPing;
+import nl.dtls.fairdatapoint.service.settings.SettingsCache;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /settings")
+public class List_GET extends WebIntegrationTest {
+
+    @Autowired
+    private SettingsRepository settingsRepository;
+
+    @Autowired
+    private SettingsCache settingsCache;
+
+    private final ParameterizedTypeReference<SettingsDTO> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/settings");
+    }
+
+    private Settings customSettings() {
+        return Settings.builder()
+                .metadataMetrics(
+                        List.of(new SettingsMetricsEntry(
+                                "http://example.com/metric",
+                                "http://example.com/resource"
+                        ))
+                )
+                .ping(SettingsPing.builder()
+                        .enabled(false)
+                        .endpoints(List.of(
+                                "https://home.fairdatapoint.org",
+                                "https://example.com/index"
+                        ))
+                        .build()
+                )
+                .build();
+    }
+
+    @Test
+    @DisplayName("HTTP 200: default settings")
+    public void res200_defaultSettings() {
+        // GIVEN: prepare data
+        Settings settings = Settings.getDefault();
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(0)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains default metrics", Objects.requireNonNull(result.getBody()).getMetadataMetrics(), is(equalTo(settings.getMetadataMetrics())));
+        assertThat("Response contains default ping enabled", Objects.requireNonNull(result.getBody()).getPing().getEnabled(), is(equalTo(settings.getPing().isEnabled())));
+        assertThat("Response contains default ping endpoints", Objects.requireNonNull(result.getBody()).getPing().getEndpoints(), is(equalTo(settings.getPing().getEndpoints())));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: custom settings")
+    public void res200_customSettings() {
+        // GIVEN: prepare data
+        Settings settings = customSettings();
+        settingsRepository.deleteAll();
+        settingsRepository.insert(settings);
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(1)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains custom metrics", Objects.requireNonNull(result.getBody()).getMetadataMetrics(), is(equalTo(settings.getMetadataMetrics())));
+        assertThat("Response contains custom ping enabled", Objects.requireNonNull(result.getBody()).getPing().getEnabled(), is(equalTo(settings.getPing().isEnabled())));
+        assertThat("Response contains custom ping endpoints", Objects.requireNonNull(result.getBody()).getPing().getEndpoints(), is(equalTo(settings.getPing().getEndpoints())));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden without auth", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: not admin")
+    public void res403_notAdmin() {
+        // GIVEN
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .header(HttpHeaders.AUTHORIZATION, NIKOLA_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden for non-admin users", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+    }
+
+    @AfterEach
+    public void teardown() {
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..60dc292c3718a8405b5d0cc963c88f73b436cf14
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/settings/List_PUT.java
@@ -0,0 +1,213 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.settings;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsDTO;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsPingUpdateDTO;
+import nl.dtls.fairdatapoint.api.dto.settings.SettingsUpdateDTO;
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
+import nl.dtls.fairdatapoint.entity.settings.Settings;
+import nl.dtls.fairdatapoint.entity.settings.SettingsMetricsEntry;
+import nl.dtls.fairdatapoint.entity.settings.SettingsPing;
+import nl.dtls.fairdatapoint.service.settings.SettingsCache;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("PUT /settings")
+public class List_PUT extends WebIntegrationTest {
+
+    @Autowired
+    private SettingsRepository settingsRepository;
+
+    @Autowired
+    private SettingsCache settingsCache;
+
+    private final ParameterizedTypeReference<SettingsDTO> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/settings");
+    }
+
+    private Settings customSettings() {
+        return Settings.builder()
+                .metadataMetrics(
+                        List.of(new SettingsMetricsEntry(
+                                "http://example.com/metric",
+                                "http://example.com/resource"
+                        ))
+                )
+                .ping(SettingsPing.builder()
+                        .enabled(false)
+                        .endpoints(List.of(
+                                "https://home.fairdatapoint.org",
+                                "https://example.com/index"
+                        ))
+                        .build()
+                )
+                .build();
+    }
+
+    private SettingsUpdateDTO customSettingsUpdateDTO() {
+        Settings customSettings = customSettings();
+        return new SettingsUpdateDTO(
+                customSettings.getMetadataMetrics(),
+                new SettingsPingUpdateDTO(
+                        customSettings.getPing().isEnabled(),
+                        customSettings.getPing().getEndpoints()
+                )
+        );
+    }
+
+    private SettingsUpdateDTO invalidUpdateDTO() {
+        Settings customSettings = customSettings();
+        return new SettingsUpdateDTO(
+                null,
+                new SettingsPingUpdateDTO(
+                        customSettings.getPing().isEnabled(),
+                        customSettings.getPing().getEndpoints()
+                )
+        );
+    }
+
+    @Test
+    @DisplayName("HTTP 200: update settings")
+    public void res200_updateSettings() {
+        // GIVEN: prepare data
+        Settings settings = customSettings();
+        settingsRepository.deleteAll();
+        settingsRepository.insert(settings);
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(customSettingsUpdateDTO());
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(1)));
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Response contains custom metrics", Objects.requireNonNull(result.getBody()).getMetadataMetrics(), is(equalTo(settings.getMetadataMetrics())));
+        assertThat("Response contains custom ping enabled", Objects.requireNonNull(result.getBody()).getPing().getEnabled(), is(equalTo(settings.getPing().isEnabled())));
+        assertThat("Response contains custom ping endpoints", Objects.requireNonNull(result.getBody()).getPing().getEndpoints(), is(equalTo(settings.getPing().getEndpoints())));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: invalid metrics")
+    public void res400_invalidList() {
+        // GIVEN: prepare data
+        SettingsUpdateDTO reqDTO = invalidUpdateDTO();
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It indicates bad request", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN: prepare data
+        SettingsUpdateDTO reqDTO = customSettingsUpdateDTO();
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden without auth", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: not admin")
+    public void res403_notAdmin() {
+        // GIVEN: prepare data
+        SettingsUpdateDTO reqDTO = customSettingsUpdateDTO();
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, NIKOLA_TOKEN)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTO);
+
+        // WHEN
+        ResponseEntity<SettingsDTO> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("It is forbidden for non-admin users", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat("No settings are created", settingsRepository.findAll().size(), is(equalTo(0)));
+    }
+
+    @AfterEach
+    public void teardown() {
+        settingsRepository.deleteAll();
+        settingsCache.updateCachedSettings();
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_DELETE.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_DELETE.java
index 5256f52f9388e11b870d2b21bcface8cdc396a71..803e6b8980aa1e930ef97f941e822058b851c5c4 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_DELETE.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_DELETE.java
@@ -23,7 +23,9 @@
 package nl.dtls.fairdatapoint.acceptance.shape;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
 import nl.dtls.fairdatapoint.database.mongo.migration.development.shape.data.ShapeFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ShapeRepository;
 import nl.dtls.fairdatapoint.entity.shape.Shape;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -53,11 +55,15 @@ public class Detail_DELETE extends WebIntegrationTest {
     @Autowired
     private ShapeFixtures shapeFixtures;
 
+    @Autowired
+    private ShapeRepository shapeRepository;
+
     @Test
     @DisplayName("HTTP 204")
     public void res204() {
         // GIVEN:
-        Shape shape = shapeFixtures.repositoryShape();
+        Shape shape = shapeFixtures.customShape();
+        shapeRepository.save(shape);
         RequestEntity<Void> request = RequestEntity
                 .delete(url(shape.getUuid()))
                 .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
@@ -73,16 +79,54 @@ public class Detail_DELETE extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 403: Shape is not authenticated")
-    public void res403_notAuthenticated() {
+    @DisplayName("HTTP 400")
+    public void res400_used() {
+        // GIVEN:
+        Shape shape = shapeFixtures.datasetShape();
+        RequestEntity<Void> request = RequestEntity
+                .delete(url(shape.getUuid()))
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .build();
+        ParameterizedTypeReference<Void> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<Void> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+    }
+
+    @Test
+    @DisplayName("HTTP 400: Delete INTERNAL shape")
+    public void res400_internal() {
+        // GIVEN:
         Shape shape = shapeFixtures.repositoryShape();
+        RequestEntity<Void> request = RequestEntity
+                .delete(url(shape.getUuid()))
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .build();
+        ParameterizedTypeReference<ErrorDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ErrorDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        Shape shape = shapeFixtures.datasetShape();
         createUserForbiddenTestDelete(client, url(shape.getUuid()));
     }
 
     @Test
-    @DisplayName("HTTP 403: Shape is not an admin")
+    @DisplayName("HTTP 403: User is not an admin")
     public void res403_shape() {
-        Shape shape = shapeFixtures.repositoryShape();
+        Shape shape = shapeFixtures.datasetShape();
         createUserForbiddenTestDelete(client, url(shape.getUuid()));
     }
 
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_PUT.java
index ffd7e7dce87e2bc5ab38834828322027e64cd5c7..c4ea8d004b389315b3bf1fd91a4c5c92bc231086 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_PUT.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Detail_PUT.java
@@ -53,7 +53,7 @@ public class Detail_PUT extends WebIntegrationTest {
     }
 
     private ShapeChangeDTO reqDto(Shape shape) {
-        return new ShapeChangeDTO(format("EDITED: %s", shape.getName()), shape.getDefinition());
+        return new ShapeChangeDTO(format("EDITED: %s", shape.getName()), false, shape.getDefinition());
     }
 
     @Autowired
@@ -89,6 +89,34 @@ public class Detail_PUT extends WebIntegrationTest {
         Common.compare(reqDto, result.getBody());
     }
 
+    @Test
+    @DisplayName("HTTP 200: Published")
+    public void res200_published() {
+        // GIVEN: Prepare data
+        Shape shape = shapeFixtures.customShapeEdited();
+        ShapeChangeDTO reqDto = reqDto(shape);
+        reqDto.setPublished(true);
+
+        // AND: Prepare request
+        RequestEntity<ShapeChangeDTO> request = RequestEntity
+                .put(url(shape.getUuid()))
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+        ParameterizedTypeReference<ShapeDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // AND: Prepare DB
+        shapeRepository.save(shape);
+
+        // WHEN:
+        ResponseEntity<ShapeDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        Common.compare(reqDto, result.getBody());
+    }
+
     @Test
     @DisplayName("HTTP 400: Invalid SHACL Definition")
     public void res400_invalidShacl() {
@@ -118,8 +146,8 @@ public class Detail_PUT extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 400: Edit INTERNAL shape")
-    public void res400() {
+    @DisplayName("HTTP 200: Edit INTERNAL shape")
+    public void res200_internal() {
         // GIVEN:
         Shape shape = shapeFixtures.repositoryShape();
         ShapeChangeDTO reqDto = reqDto(shape);
@@ -128,25 +156,26 @@ public class Detail_PUT extends WebIntegrationTest {
                 .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
                 .accept(MediaType.APPLICATION_JSON)
                 .body(reqDto);
-        ParameterizedTypeReference<ErrorDTO> responseType = new ParameterizedTypeReference<>() {
+        ParameterizedTypeReference<ShapeDTO> responseType = new ParameterizedTypeReference<>() {
         };
 
         // WHEN:
-        ResponseEntity<ErrorDTO> result = client.exchange(request, responseType);
+        ResponseEntity<ShapeDTO> result = client.exchange(request, responseType);
 
         // THEN:
-        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        Common.compare(reqDto, result.getBody());
     }
 
     @Test
-    @DisplayName("HTTP 403: Shape is not authenticated")
+    @DisplayName("HTTP 403: User is not authenticated")
     public void res403_notAuthenticated() {
         Shape shape = shapeFixtures.repositoryShape();
         createNoUserForbiddenTestPut(client, url(shape.getUuid()), reqDto(shape));
     }
 
     @Test
-    @DisplayName("HTTP 403: Shape is not an admin")
+    @DisplayName("HTTP 403: User is not an admin")
     public void res403_shape() {
         Shape shape = shapeFixtures.repositoryShape();
         createUserForbiddenTestPut(client, url(shape.getUuid()), reqDto(shape));
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Import_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Import_POST.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5998516e96c31b5bbcc5cfdb7072dc804be0ac1
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Import_POST.java
@@ -0,0 +1,202 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.shape;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeDTO;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeRemoteDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.shape.data.ShapeFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ShapeRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /shapes/import")
+public class Import_POST extends WebIntegrationTest {
+
+    @Autowired
+    private ShapeRepository shapeRepository;
+
+    @Autowired
+    private ShapeFixtures shapeFixtures;
+
+    private final ParameterizedTypeReference<List<ShapeDTO>> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/shapes/import");
+    }
+
+    private ShapeRemoteDTO shapeRemoteDTO1() {
+        return new ShapeRemoteDTO(
+                "http://example.com",
+                UUID.randomUUID().toString(),
+                shapeFixtures.customShape().getName(),
+                shapeFixtures.customShape().getDefinition()
+        );
+    }
+
+    private ShapeRemoteDTO shapeRemoteDTO2() {
+        return new ShapeRemoteDTO(
+                "http://example.com",
+                UUID.randomUUID().toString(),
+                shapeFixtures.customShapeEdited().getName(),
+                shapeFixtures.customShapeEdited().getDefinition()
+        );
+    }
+
+    @Test
+    @DisplayName("HTTP 200: empty import")
+    public void res200_emptyImport() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        List<ShapeRemoteDTO> reqDTOs = Collections.emptyList();
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .body(reqDTOs);
+
+        // WHEN:
+        ResponseEntity<List<ShapeDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Result is an empty list", result.getBody().size(), is(equalTo(0)));
+        assertThat("Shape repository is empty", shapeRepository.count(), is(equalTo(0L)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: single import")
+    public void res200_singleImport() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        ShapeRemoteDTO shape = shapeRemoteDTO1();
+        List<ShapeRemoteDTO> reqDTOs = Collections.singletonList(shape);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .body(reqDTOs);
+
+        // WHEN:
+        ResponseEntity<List<ShapeDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Result contains one shape", result.getBody().size(), is(equalTo(1)));
+        assertThat("Shape is in the shape repository", shapeRepository.count(), is(equalTo(1L)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: multiple import")
+    public void res200_multipleImport() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        ShapeRemoteDTO shape1 = shapeRemoteDTO1();
+        ShapeRemoteDTO shape2 = shapeRemoteDTO2();
+        List<ShapeRemoteDTO> reqDTOs = Arrays.asList(shape1, shape2);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .header(HttpHeaders.AUTHORIZATION, ADMIN_TOKEN)
+                .body(reqDTOs);
+
+        // WHEN:
+        ResponseEntity<List<ShapeDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Result contains one shape", result.getBody().size(), is(equalTo(2)));
+        assertThat("Shape is in the shape repository", shapeRepository.count(), is(equalTo(2L)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: no token")
+    public void res403_noToken() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        ShapeRemoteDTO shape = shapeRemoteDTO1();
+        List<ShapeRemoteDTO> reqDTOs = Collections.singletonList(shape);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDTOs);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat("Shape repository stays empty", shapeRepository.count(), is(equalTo(0L)));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: non-admin token")
+    public void res403_nonAdminToken() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        ShapeRemoteDTO shape1 = shapeRemoteDTO1();
+        ShapeRemoteDTO shape2 = shapeRemoteDTO2();
+        List<ShapeRemoteDTO> reqDTOs = Arrays.asList(shape1, shape2);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .post(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .body(reqDTOs);
+
+        // WHEN
+        ResponseEntity<Void> result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+        // THEN:
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.FORBIDDEN)));
+        assertThat("Shape repository stays empty", shapeRepository.count(), is(equalTo(0L)));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/List_POST.java
index faacaacc2ae6dd1d415db732a78b144810c9a6bc..4da914265c8c9044de72435974b404b3ce96238c 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/List_POST.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/List_POST.java
@@ -53,7 +53,7 @@ public class List_POST extends WebIntegrationTest {
     }
 
     private ShapeChangeDTO reqDto(Shape shape) {
-        return new ShapeChangeDTO(shape.getName(), shape.getDefinition());
+        return new ShapeChangeDTO(shape.getName(), false, shape.getDefinition());
     }
 
     @Test
@@ -106,13 +106,13 @@ public class List_POST extends WebIntegrationTest {
     }
 
     @Test
-    @DisplayName("HTTP 403: Shape is not authenticated")
+    @DisplayName("HTTP 403: User is not authenticated")
     public void res403_notAuthenticated() {
         createNoUserForbiddenTestPost(client, url(), reqDto(shapeFixtures.customShape()));
     }
 
     @Test
-    @DisplayName("HTTP 403: Shape is not an admin")
+    @DisplayName("HTTP 403: User is not an admin")
     public void res403_shape() {
         createUserForbiddenTestPost(client, url(), reqDto(shapeFixtures.customShape()));
     }
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Public_GET.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Public_GET.java
new file mode 100644
index 0000000000000000000000000000000000000000..e61bc63e2f7b72aeb24ebb0ac23d2c2c3b43506b
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/shape/Public_GET.java
@@ -0,0 +1,124 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.shape;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.shape.ShapeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.shape.data.ShapeFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ShapeRepository;
+import nl.dtls.fairdatapoint.entity.shape.Shape;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /shapes/public")
+public class Public_GET extends WebIntegrationTest {
+
+    @Autowired
+    private ShapeRepository shapeRepository;
+
+    @Autowired
+    private ShapeFixtures shapeFixtures;
+
+    private final ParameterizedTypeReference<List<ShapeDTO>> responseType =
+            new ParameterizedTypeReference<>() {
+            };
+
+    private URI url() {
+        return URI.create("/shapes/public");
+    }
+
+    private Shape makeShape(Boolean published) {
+        return new Shape().toBuilder()
+                .uuid(UUID.randomUUID().toString())
+                .name(shapeFixtures.customShape().getName())
+                .definition(shapeFixtures.customShape().getDefinition())
+                .published(published)
+                .targetClasses(Set.of())
+                .build();
+    }
+
+    @Test
+    @DisplayName("HTTP 200: no published")
+    public void res200_noPublished() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        Shape shape = makeShape(false);
+        shapeRepository.insert(shape);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN:
+        ResponseEntity<List<ShapeDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Result is an empty list", result.getBody().size(), is(equalTo(0)));
+    }
+
+    @Test
+    @DisplayName("HTTP 200: published")
+    public void res200_published() {
+        // GIVEN: prepare data
+        shapeRepository.deleteAll();
+        Shape shapeNotPublished = makeShape(false);
+        Shape shapePublished = makeShape(true);
+        shapeRepository.insert(shapeNotPublished);
+        shapeRepository.insert(shapePublished);
+
+        // AND: prepare request
+        RequestEntity<?> request = RequestEntity
+                .get(url())
+                .accept(MediaType.APPLICATION_JSON)
+                .build();
+
+        // WHEN:
+        ResponseEntity<List<ShapeDTO>> result = client.exchange(request, responseType);
+
+        // THEN
+        assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat("Response body is not null", result.getBody(), is(notNullValue()));
+        assertThat("Result is an empty list", result.getBody().size(), is(equalTo(1)));
+        assertThat("UUID matches the published shape", result.getBody().get(0).getUuid(), is(equalTo(shapePublished.getUuid())));
+    }
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/token/List_POST.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/token/List_POST.java
index 85c7b7dbe8cf6cb3a45c96503949cf4a8e6a86be..2f53ffb487bad9d849a20e7118a618140632a53b 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/token/List_POST.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/token/List_POST.java
@@ -23,21 +23,22 @@
 package nl.dtls.fairdatapoint.acceptance.token;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.acceptance.user.Common;
 import nl.dtls.fairdatapoint.api.dto.auth.AuthDTO;
 import nl.dtls.fairdatapoint.api.dto.auth.TokenDTO;
 import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
+import nl.dtls.fairdatapoint.api.dto.user.UserDTO;
+import nl.dtls.fairdatapoint.entity.user.User;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.RequestEntity;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.*;
 
 import java.net.URI;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.anything;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 
@@ -45,8 +46,8 @@ import static org.hamcrest.core.IsEqual.equalTo;
 public class List_POST extends WebIntegrationTest {
 
     @Test
-    @DisplayName("HTTP 200")
-    public void res200() {
+    @DisplayName("HTTP 200: login")
+    public void res200_login() {
         // GIVEN:
         AuthDTO reqDto = new AuthDTO("albert.einstein@example.com", "password");
         RequestEntity<AuthDTO> request = RequestEntity
@@ -61,6 +62,7 @@ public class List_POST extends WebIntegrationTest {
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result.getBody(), is(notNullValue()));
         assertThat(result.getBody().getToken(), is(anything()));
     }
 
@@ -81,7 +83,44 @@ public class List_POST extends WebIntegrationTest {
 
         // THEN:
         assertThat(result.getStatusCode(), is(equalTo(HttpStatus.UNAUTHORIZED)));
+        assertThat(result.getBody(), is(notNullValue()));
         assertThat(result.getBody().getMessage(), is("Invalid username/password supplied"));
     }
 
+    @Test
+    @DisplayName("HTTP 200: login and get current")
+    public void res200() {
+        //= Login (get token)
+        // GIVEN:
+        AuthDTO reqDto = new AuthDTO("albert.einstein@example.com", "password");
+        RequestEntity<AuthDTO> request1 = RequestEntity
+                .post(URI.create("/tokens"))
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+        ParameterizedTypeReference<TokenDTO> responseType1 = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<TokenDTO> result1 = client.exchange(request1, responseType1);
+
+        // THEN:
+        assertThat(result1.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        assertThat(result1.getBody(), is(notNullValue()));
+        assertThat(result1.getBody().getToken(), is(anything()));
+
+        //= Get current user with the token
+        // GIVEN:
+        RequestEntity<Void> request2 = RequestEntity
+                .get(URI.create("/users/current"))
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + result1.getBody().getToken())
+                .build();
+        ParameterizedTypeReference<UserDTO> responseType2 = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<UserDTO> result2 = client.exchange(request2, responseType2);
+
+        // THEN:
+        assertThat(result2.getStatusCode(), is(equalTo(HttpStatus.OK)));
+    }
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Common.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Common.java
index df2fabfd7347354c960db3d049eb1b9ff857c603..0218fc27f8ecdcd353e0aa934789b6081518a1ee 100755
--- a/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Common.java
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Common.java
@@ -25,6 +25,7 @@ package nl.dtls.fairdatapoint.acceptance.user;
 import nl.dtls.fairdatapoint.api.dto.user.UserChangeDTO;
 import nl.dtls.fairdatapoint.api.dto.user.UserCreateDTO;
 import nl.dtls.fairdatapoint.api.dto.user.UserDTO;
+import nl.dtls.fairdatapoint.api.dto.user.UserProfileChangeDTO;
 import nl.dtls.fairdatapoint.entity.user.User;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -45,6 +46,12 @@ public class Common {
         assertThat(dto.getEmail(), is(equalTo(entity.getEmail())));
     }
 
+    public static void compare(UserProfileChangeDTO entity, UserDTO dto) {
+        assertThat(dto.getFirstName(), is(equalTo(entity.getFirstName())));
+        assertThat(dto.getLastName(), is(equalTo(entity.getLastName())));
+        assertThat(dto.getEmail(), is(equalTo(entity.getEmail())));
+    }
+
     public static void compare(User entity, UserDTO dto) {
         assertThat(dto.getUuid(), is(equalTo(entity.getUuid())));
         assertThat(dto.getFirstName(), is(equalTo(entity.getFirstName())));
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Detail_Current_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Detail_Current_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..2dbba395a68a650c49ad5a4d5a32c78c65cf7040
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Detail_Current_PUT.java
@@ -0,0 +1,108 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.user;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.error.ErrorDTO;
+import nl.dtls.fairdatapoint.api.dto.user.UserDTO;
+import nl.dtls.fairdatapoint.api.dto.user.UserProfileChangeDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /users/:userUuid")
+public class Detail_Current_PUT extends WebIntegrationTest {
+
+    private URI url() {
+        return URI.create("/users/current");
+    }
+
+    private UserProfileChangeDTO reqDto() {
+        return new UserProfileChangeDTO("EDITED: Albert", "EDITED: Einstein", "albert.einstein.edited@example.com");
+    }
+
+    @Autowired
+    private UserFixtures userFixtures;
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        RequestEntity<UserProfileChangeDTO> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto());
+        ParameterizedTypeReference<UserDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<UserDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        Common.compare(reqDto(), result.getBody());
+    }
+
+    @Test
+    @DisplayName("HTTP 400: Email Already Exists")
+    public void res400_emailAlreadyExists() {
+        // GIVEN:
+        UserProfileChangeDTO reqDto = new UserProfileChangeDTO(
+                "EDITED: Albert",
+                "EDITED: Einstein",
+                "nikola.tesla@example.com");
+        RequestEntity<UserProfileChangeDTO> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .accept(MediaType.APPLICATION_JSON)
+                .body(reqDto);
+        ParameterizedTypeReference<ErrorDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<ErrorDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+        assertThat(result.getBody().getMessage(), is(format("Email '%s' is already taken", reqDto.getEmail())));
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPut(client, url(), reqDto());
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Detail_Current_Password_PUT.java b/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Detail_Current_Password_PUT.java
new file mode 100644
index 0000000000000000000000000000000000000000..1061af9bf30cf5da1950424244c72b239aa32532
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/acceptance/user/Detail_Current_Password_PUT.java
@@ -0,0 +1,86 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.acceptance.user;
+
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.api.dto.user.UserDTO;
+import nl.dtls.fairdatapoint.api.dto.user.UserPasswordDTO;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.user.data.UserFixtures;
+import nl.dtls.fairdatapoint.entity.user.User;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static nl.dtls.fairdatapoint.acceptance.common.ForbiddenTest.createNoUserForbiddenTestPut;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("PUT /users/current/password")
+public class Detail_Current_Password_PUT extends WebIntegrationTest {
+
+    private URI url() {
+        return URI.create("/users/current/password");
+    }
+
+    private UserPasswordDTO reqDto() {
+        return new UserPasswordDTO("newPassword");
+    }
+
+    @Autowired
+    private UserFixtures userFixtures;
+
+    @Test
+    @DisplayName("HTTP 200")
+    public void res200() {
+        // GIVEN:
+        User user = userFixtures.albert();
+        RequestEntity<UserPasswordDTO> request = RequestEntity
+                .put(url())
+                .header(HttpHeaders.AUTHORIZATION, ALBERT_TOKEN)
+                .body(reqDto());
+        ParameterizedTypeReference<UserDTO> responseType = new ParameterizedTypeReference<>() {
+        };
+
+        // WHEN:
+        ResponseEntity<UserDTO> result = client.exchange(request, responseType);
+
+        // THEN:
+        assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+        Common.compare(user, result.getBody());
+    }
+
+    @Test
+    @DisplayName("HTTP 403: User is not authenticated")
+    public void res403_notAuthenticated() {
+        createNoUserForbiddenTestPut(client, url(), reqDto());
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/config/MetadataTestConfig.java b/src/test/java/nl/dtls/fairdatapoint/config/MetadataTestConfig.java
index f1f3a0dcf6c35f13e00ae0f729abda215c9c2f0f..446e33242c30e5431c38f8d1536467648d4e358a 100755
--- a/src/test/java/nl/dtls/fairdatapoint/config/MetadataTestConfig.java
+++ b/src/test/java/nl/dtls/fairdatapoint/config/MetadataTestConfig.java
@@ -22,7 +22,6 @@
  */
 package nl.dtls.fairdatapoint.config;
 
-import nl.dtls.fairdatapoint.entity.metadata.Agent;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.ValueFactory;
 import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
@@ -34,14 +33,6 @@ public class MetadataTestConfig {
 
     private final ValueFactory VF = SimpleValueFactory.getInstance();
 
-    @Bean(name = "publisher")
-    public Agent publisher() {
-        Agent publisher = new Agent();
-        publisher.setUri(VF.createIRI("https://www.dtls.nl"));
-        publisher.setName(VF.createLiteral("DTLS"));
-        return publisher;
-    }
-
     @Bean(name = "language")
     public IRI language() {
         return VF.createIRI("http://id.loc.gov/vocabulary/iso639-1/en");
diff --git a/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/catalog/CatalogMetadataRepositoryTest.java b/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/catalog/CatalogMetadataRepositoryTest.java
index b88fba2bde9e0d867099e1494a972a2f1661d163..a67e2b6941d188631cd6b8a2b0ad4f3326439048 100755
--- a/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/catalog/CatalogMetadataRepositoryTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/catalog/CatalogMetadataRepositoryTest.java
@@ -48,7 +48,7 @@ import static org.mockito.Mockito.*;
 @ExtendWith(MockitoExtension.class)
 public class CatalogMetadataRepositoryTest {
 
-    private IRI catalogUri = i("http://localhost/textmining");
+    private final IRI catalogUri = i("http://localhost/textmining");
 
     @Mock
     private Cache cache;
diff --git a/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepositoryTest.java b/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepositoryTest.java
index d6366632b310524de5b5cd01bc0445016beb70c7..e7a4c938ac6597e326cd6753787bfffaafb0e7a9 100755
--- a/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepositoryTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/common/MetadataRepositoryTest.java
@@ -28,256 +28,113 @@
 package nl.dtls.fairdatapoint.database.rdf.repository.common;
 
 import nl.dtls.fairdatapoint.WebIntegrationTest;
-import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
 import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepository;
-import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepositoryImpl;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.model.Statement;
-import org.eclipse.rdf4j.model.ValueFactory;
-import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
-import org.eclipse.rdf4j.model.vocabulary.RDF;
-import org.eclipse.rdf4j.repository.Repository;
-import org.eclipse.rdf4j.repository.RepositoryException;
-import org.junit.jupiter.api.BeforeEach;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
 import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.test.annotation.DirtiesContext;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.when;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getLanguage;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
 
-// TODO Get rid of DirtiesContext
-@DirtiesContext
 public class MetadataRepositoryTest extends WebIntegrationTest {
 
-    @Autowired
-    @Qualifier("persistentUrl")
-    private String persistentUrl;
-
-    private final ValueFactory f = SimpleValueFactory.getInstance();
-
-    private List<Statement> STATEMENTS;
-
-    private final IRI TESTSUB = f.createIRI("http://www.dtls.nl/testSub");
-
-    private final IRI TESTOBJ = f.createIRI("http://www.dtls.nl/testObj");
-
-    private final Statement TESTSTMT = f.createStatement(TESTSUB, RDF.TYPE, TESTOBJ);
-
     @Autowired
     private GenericMetadataRepository metadataRepository;
 
-    @Mock
-    private Repository repository;
-
-    @InjectMocks
-    private GenericMetadataRepositoryImpl mockMetadataRepository;
-
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
-
-    @BeforeEach
-    public void storeExampleFile() throws MetadataRepositoryException {
-        STATEMENTS = new ArrayList<>(testMetadataFixtures.repositoryMetadata());
-
-        metadataRepository.storeStatements(STATEMENTS, f.createIRI(persistentUrl));
-        MockitoAnnotations.initMocks(this);
-    }
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
-    /**
-     * The URI of a RDF resource can't be NULL, this test is excepted to throw
-     * IllegalArgumentException
-     */
-    @DirtiesContext
     @Test
-    public void nullURI() throws MetadataRepositoryException {
-        assertThrows(NullPointerException.class, () -> {
-            metadataRepository.retrieveResource(null);
-        });
-    }
+    public void findWorks() throws Exception {
+        // GIVEN:
+        IRI context = getUri(testMetadataFixtures.catalog1());
 
-    /**
-     * The URI of a RDF resource can't be EMPTY, this test is excepted to throw
-     * IllegalArgumentException
-     */
-    @DirtiesContext
-    @Test
-    public void emptyURI() throws MetadataRepositoryException {
-        assertThrows(IllegalArgumentException.class, () -> {
+        // WHEN:
+        List<Statement> result = metadataRepository.find(context);
 
-            String uri = "";
-            metadataRepository.retrieveResource(f.createIRI(uri));
-        });
+        // THEN:
+        assertThat(result.size(), is(equalTo(30)));
     }
 
-    /**
-     * This test is excepted to throw execption
-     */
-    @DirtiesContext
     @Test
-    public void emptyInvalidURI() throws MetadataRepositoryException {
-        assertThrows(IllegalArgumentException.class, () -> {
-            String uri = "...";
-            metadataRepository.retrieveResource(f.createIRI(uri));
-        });
-    }
+    public void findNonExistingResource() throws Exception {
+        // GIVEN:
+        IRI context = i("http://localhost/non-existing");
 
-    /**
-     * The test is excepted to retrieve ZERO statements
-     */
-    @DirtiesContext
-    @Test
-    public void retrieveNonExitingResource() throws Exception {
+        // WHEN:
+        List<Statement> result = metadataRepository.find(context);
 
-        String uri = "http://localhost/dummy";
-        List<Statement> statements = metadataRepository.retrieveResource(f.createIRI(uri));
-        assertTrue(statements.isEmpty());
+        // THEN:
+        assertThat(result.size(), is(equalTo(0)));
     }
 
-    /**
-     * The test is excepted retrieve to retrieve one or more statements
-     */
-    @DirtiesContext
     @Test
-    public void retrieveExitingResource() throws Exception {
+    public void checkExistenceWorks() throws Exception {
+        // GIVEN:
+        Model metadata = testMetadataFixtures.catalog1();
 
-        List<Statement> statements = metadataRepository.retrieveResource(
-                f.createIRI(persistentUrl));
-        assertTrue(statements.size() > 0);
+        // WHEN:
+        boolean result = metadataRepository.checkExistence(getUri(metadata), DCTERMS.LANGUAGE, getLanguage(metadata));
+
+        // THEN:
+        assertThat(result, is(equalTo(true)));
     }
 
-    /**
-     * The test is excepted to throw error
-     */
-    @DirtiesContext
     @Test
-    public void retrieveResourceCatchBlock() throws Exception {
-        assertThrows(MetadataRepositoryException.class, () -> {
+    public void saveWorks() throws Exception {
+        // GIVEN:
+        Model metadata = testMetadataFixtures.c1_d2_distribution3();
+        IRI context = getUri(metadata);
+        ArrayList<Statement> statements = new ArrayList<>(metadata);
 
-            when(repository.getConnection()).thenThrow(RepositoryException.class);
-            mockMetadataRepository.retrieveResource(f.createIRI(persistentUrl));
-        });
-    }
+        // WHEN:
+        metadataRepository.save(statements, context);
 
-    /**
-     * The test is excepted to pass
-     */
-    @DirtiesContext
-    @Test
-    public void storeResource() {
-        try {
-            metadataRepository.storeStatements(STATEMENTS);
-        } catch (MetadataRepositoryException ex) {
-            fail("The test is not excepted to throw MetadataRepositoryException");
-        }
+        // THEN:
+        assertThat(metadataRepository.find(context).size(), is(equalTo(28)));
     }
 
-    /**
-     * The test is excepted to pass
-     */
-    @DirtiesContext
     @Test
-    public void deleteRource() {
-        try {
-            List<Statement> sts = new ArrayList<>();
-            sts.add(TESTSTMT);
-            metadataRepository.storeStatements(sts);
-            metadataRepository.removeStatement(TESTSUB, RDF.TYPE, null);
-        } catch (MetadataRepositoryException ex) {
-            fail("The test is not excepted to throw MetadataRepositoryException");
-        }
-    }
+    public void removeWorks() throws Exception {
+        // GIVEN:
+        Model metadata = testMetadataFixtures.catalog1();
+        IRI context = getUri(metadata);
 
-    /**
-     * The test is excepted to pass
-     */
-    @DirtiesContext
-    @Test
-    public void storeStatement() {
-        try {
-            metadataRepository.storeStatements(STATEMENTS, TESTSUB);
-        } catch (MetadataRepositoryException ex) {
-            fail("The test is not excepted to throw MetadataRepositoryException");
-        }
-    }
+        // AND: Check existence before delete
+        assertThat(metadataRepository.find(context).size(), is(equalTo(30)));
 
-    /**
-     * The test is excepted to retrieve return false
-     */
-    @DirtiesContext
-    @Test
-    public void checkNonExitingResource() throws Exception {
-        String uri = "http://localhost/dummy";
-        boolean isStatementExist = metadataRepository.isStatementExist(f.createIRI(uri), null, null);
-        assertFalse(isStatementExist);
-    }
+        // WHEN:
+        metadataRepository.remove(context);
 
-    /**
-     * The test is excepted to retrieve return true
-     */
-    @DirtiesContext
-    @Test
-    public void checkExitingResource() throws Exception {
-        boolean isStatementExist = metadataRepository.isStatementExist(
-                f.createIRI(persistentUrl), null, null);
-        assertTrue(isStatementExist);
+        // THEN:
+        assertThat(metadataRepository.find(context).size(), is(equalTo(0)));
     }
 
-    /**
-     * Check exception handling of delete resource method
-     */
-    @DirtiesContext
     @Test
-    public void checkExceptionsDeleteResourceMethod() throws Exception {
-        assertThrows(MetadataRepositoryException.class, () -> {
-            when(repository.getConnection()).thenThrow(RepositoryException.class);
-            mockMetadataRepository.removeResource(null);
-        });
-    }
+    public void removeStatementWorks() throws Exception {
+        // GIVEN:
+        Model metadata = testMetadataFixtures.catalog1();
+        IRI context = getUri(metadata);
 
-    /**
-     * Check exception handling of remove statement method
-     */
-    @DirtiesContext
-    @Test
-    public void checkExceptionsRemoveStatementMethod() throws Exception {
-        assertThrows(MetadataRepositoryException.class, () -> {
-            when(repository.getConnection()).thenThrow(RepositoryException.class);
-            mockMetadataRepository.removeStatement(null, null, null);
-        });
-    }
+        // AND: Check existence before delete
+        assertThat(metadataRepository.find(context).size(), is(equalTo(30)));
 
-    /**
-     * Check exception handling of isStatementExist method
-     */
-    @DirtiesContext
-    @Test
-    public void checkExceptionsIsStatementMethod() throws Exception {
-        assertThrows(MetadataRepositoryException.class, () -> {
-            when(repository.getConnection()).thenThrow(RepositoryException.class);
-            mockMetadataRepository.isStatementExist(f.createIRI(persistentUrl),
-                    null, null);
-        });
-    }
+        // WHEN:
+        metadataRepository.removeStatement(getUri(metadata), DCTERMS.LANGUAGE, getLanguage(metadata), context);
 
-    /**
-     * Check exception handling of storeStatement method
-     */
-    @DirtiesContext
-    @Test
-    public void checkExceptionsStoreStatementMethod() throws Exception {
-        assertThrows(MetadataRepositoryException.class, () -> {
-            when(repository.getConnection()).thenThrow(RepositoryException.class);
-            mockMetadataRepository.storeStatements(STATEMENTS);
-        });
+        // THEN:
+        assertThat(metadataRepository.find(context).size(), is(equalTo(29)));
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryTest.java b/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryTest.java
index e6aacbf3dfcf3a9e364abcf4f985156a383c8542..4e54801dd3cac57ee75cf4b9d9d4e66184ea308e 100755
--- a/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/database/rdf/repository/generic/GenericMetadataRepositoryTest.java
@@ -22,99 +22,108 @@
  */
 package nl.dtls.fairdatapoint.database.rdf.repository.generic;
 
+import nl.dtls.fairdatapoint.WebIntegrationTest;
+import nl.dtls.fairdatapoint.database.rdf.repository.catalog.CatalogMetadataRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Resource;
-import org.eclipse.rdf4j.model.Value;
-import org.eclipse.rdf4j.repository.Repository;
-import org.eclipse.rdf4j.repository.RepositoryConnection;
-import org.junit.jupiter.api.BeforeEach;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.Cache;
 import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
 
-import java.util.Collections;
+import java.util.ArrayList;
 
 import static nl.dtls.fairdatapoint.config.CacheConfig.CATALOG_THEMES_CACHE;
-import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
-import static org.mockito.Mockito.*;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getLanguage;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
 
-public class GenericMetadataRepositoryTest {
+public class GenericMetadataRepositoryTest extends WebIntegrationTest {
 
-    private IRI catalogUri = i("http://localhost/textmining");
-
-    @Spy
-    private Cache cache;
-
-    @Mock
-    private Repository repository;
-
-    @Mock
-    private RepositoryConnection repositoryConnection;
-
-    @Mock
-    private Resource resource;
-
-    @Mock
-    private Value value;
-
-    @Mock
+    @Autowired
     private ConcurrentMapCacheManager cacheManager;
 
-    @InjectMocks
-    @Spy
-    private GenericMetadataRepositoryImpl metadataRepository;
+    @Autowired
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
-    @BeforeEach
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
+    @Autowired
+    private GenericMetadataRepository metadataRepository;
+
+    @Autowired
+    private CatalogMetadataRepository catalogMetadataRepository;
 
     @Test
-    @DisplayName("'storeStatements' should evict cache")
-    public void storeStatementsEvictsCache() throws MetadataRepositoryException {
+    @DisplayName("'save' should evict cache")
+    public void saveEvictsCache() throws MetadataRepositoryException {
         // GIVEN:
-        when(cacheManager.getCache(CATALOG_THEMES_CACHE)).thenReturn(cache);
-        when(repository.getConnection()).thenReturn(repositoryConnection);
+        Model catalog = testMetadataFixtures.catalog1();
+        IRI catalogUri = getUri(catalog);
+        Model dataset = testMetadataFixtures.c1_dataset1();
+
+        // AND: Compute cache
+        catalogMetadataRepository.getDatasetThemesForCatalog(catalogUri);
+
+        // AND: Check if cache is full
+        assertThat(getCache().get(catalogUri.stringValue()), is(notNullValue()));
 
         // WHEN:
-        metadataRepository.storeStatements(Collections.emptyList(), catalogUri);
+        metadataRepository.save(new ArrayList<>(dataset), getUri(dataset));
 
         // THEN:
-        verify(cache, times(1)).evict(catalogUri.toString());
+        assertThat(getCache().get(catalogUri.stringValue()), is(nullValue()));
     }
 
     @Test
     @DisplayName("'removeStatement' should evict cache")
     public void removeStatementEvictsCache() throws MetadataRepositoryException {
         // GIVEN:
-        when(cacheManager.getCache(CATALOG_THEMES_CACHE)).thenReturn(cache);
-        when(repository.getConnection()).thenReturn(repositoryConnection);
+        Model catalog = testMetadataFixtures.catalog1();
+        IRI catalogUri = getUri(catalog);
+        Model dataset = testMetadataFixtures.c1_dataset1();
+
+        // AND: Compute cache
+        catalogMetadataRepository.getDatasetThemesForCatalog(catalogUri);
+
+        // AND: Check if cache is full
+        assertThat(getCache().get(catalogUri.stringValue()), is(notNullValue()));
 
         // WHEN:
-        metadataRepository.removeStatement(resource, catalogUri, value);
+        metadataRepository.removeStatement(getUri(dataset), DCTERMS.LANGUAGE, getLanguage(dataset), getUri(dataset));
 
         // THEN:
-        verify(cache, times(1)).evict(catalogUri.toString());
+        assertThat(getCache().get(catalogUri.stringValue()), is(nullValue()));
     }
 
     @Test
-    @DisplayName("'removeResource' should evict cache")
-    public void removeResourceEvictsCache() throws MetadataRepositoryException {
+    @DisplayName("'remove' should evict cache")
+    public void removeEvictsCache() throws MetadataRepositoryException {
         // GIVEN:
-        when(cacheManager.getCache(CATALOG_THEMES_CACHE)).thenReturn(cache);
-        when(repository.getConnection()).thenReturn(repositoryConnection);
+        Model catalog = testMetadataFixtures.catalog1();
+        IRI catalogUri = getUri(catalog);
+        Model dataset = testMetadataFixtures.c1_dataset1();
+
+        // AND: Compute cache
+        catalogMetadataRepository.getDatasetThemesForCatalog(catalogUri);
+
+        // AND: Check if cache is full
+        assertThat(getCache().get(catalogUri.stringValue()), is(notNullValue()));
 
         // WHEN:
-        metadataRepository.removeResource(catalogUri);
+        metadataRepository.remove(getUri(dataset));
 
         // THEN:
-        verify(cache, times(1)).evict(catalogUri.toString());
+        assertThat(getCache().get(catalogUri.stringValue()), is(nullValue()));
+    }
+
+    private Cache getCache() {
+        return cacheManager.getCache(CATALOG_THEMES_CACHE);
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/index/harvester/HarvesterServiceTest.java b/src/test/java/nl/dtls/fairdatapoint/service/index/harvester/HarvesterServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..62c7a0b01541e223e4f230028d2c78646bb19318
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/service/index/harvester/HarvesterServiceTest.java
@@ -0,0 +1,149 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.index.harvester;
+
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.data.RdfMetadataFixtures;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.factory.MetadataFactoryImpl;
+import nl.dtls.fairdatapoint.database.rdf.repository.exception.MetadataRepositoryException;
+import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.metadata.enhance.MetadataEnhancer;
+import nl.dtls.fairdatapoint.service.profile.ProfileService;
+import nl.dtls.fairdatapoint.service.resource.ResourceDefinitionCache;
+import nl.dtls.fairdatapoint.vocabulary.R3D;
+import org.eclipse.rdf4j.model.Model;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
+import static nl.dtls.fairdatapoint.util.RdfIOUtil.write;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class HarvesterServiceTest {
+
+    @Mock
+    private RestTemplate restTemplate;
+
+    @Mock
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    @Spy
+    private GenericMetadataRepository genericMetadataRepository;
+
+    @InjectMocks
+    private static MetadataEnhancer metadataEnhancer;
+
+    @InjectMocks
+    private HarvesterService harvesterService;
+
+    private final String repositoryUrl = "http://fairdatapoint.example";
+
+    private Model repository;
+
+    private final String catalogUrl = "http://fairdatapoint.example/catalog/catalog-1";
+
+    private Model catalog;
+
+    @BeforeEach
+    private void setup() {
+        // Setup resource definition;
+        ResourceDefinitionFixtures resourceDefinitionFixtures = new ResourceDefinitionFixtures();
+
+        // Setup RDF fixtures
+        RdfMetadataFixtures fixtures = new RdfMetadataFixtures(new MetadataFactoryImpl());
+
+        // Create repository
+        repository = fixtures.repositoryMetadata(repositoryUrl);
+        ResourceDefinition rdRepository = resourceDefinitionFixtures.repositoryDefinition();
+
+        // Create catalog
+        catalog = fixtures.catalog1(repositoryUrl, getUri(repository));
+        repository.add(i(repositoryUrl), R3D.DATACATALOG, i(catalogUrl));
+    }
+
+    @Test
+    public void harvestSucceed() throws MetadataRepositoryException {
+        // GIVEN: Mock webserver
+        mockEndpoint(repositoryUrl, repository);
+        mockEndpoint(catalogUrl, catalog);
+
+        // WHEN:
+        harvesterService.harvest(repositoryUrl);
+
+        // THEN:
+        verify(genericMetadataRepository, times(1)).save(anyList(), eq(i(repositoryUrl)));
+        verify(genericMetadataRepository, times(1)).save(anyList(), eq(i(catalogUrl)));
+    }
+
+    @Test
+    public void harvestFailedForLinkedChildren() throws MetadataRepositoryException {
+        // GIVEN: Mock webserver
+        mockEndpoint(repositoryUrl, repository);
+        mockEndpoint404(catalogUrl);
+
+        // WHEN:
+        harvesterService.harvest(repositoryUrl);
+
+        // THEN:
+        verify(genericMetadataRepository, times(1)).save(anyList(), eq(i(repositoryUrl)));
+        verify(genericMetadataRepository, times(0)).save(anyList(), eq(i(catalogUrl)));
+    }
+
+    private void mockEndpoint(String url, Model body) {
+        // Create response
+        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+        headers.set("Content-Type", "text/turtle");
+        ResponseEntity<String> responseBody = new ResponseEntity<>(write(body), headers, HttpStatus.OK);
+
+        // Mock
+        when(restTemplate.exchange(eq(url), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
+                .thenReturn(responseBody);
+    }
+
+    private void mockEndpoint404(String url) {
+        // Create response
+        ResponseEntity<String> responseBody = new ResponseEntity<>("", HttpStatus.NOT_FOUND);
+
+        // Mock
+        when(restTemplate.exchange(eq(url), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
+                .thenReturn(responseBody);
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceMockTest.java b/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceMockTest.java
index 586dfde217587dc17b899fc6043d4d617855112a..a061880269a3b3001deb0cba0877f4556c266234 100755
--- a/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceMockTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceMockTest.java
@@ -25,7 +25,7 @@ package nl.dtls.fairdatapoint.service.metadata.catalog;
 import nl.dtls.fairdatapoint.BaseIntegrationTest;
 import nl.dtls.fairdatapoint.database.rdf.repository.catalog.CatalogMetadataRepository;
 import nl.dtls.fairdatapoint.database.rdf.repository.generic.GenericMetadataRepositoryImpl;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
 import org.eclipse.rdf4j.model.IRI;
 import org.eclipse.rdf4j.model.Model;
 import org.eclipse.rdf4j.model.Statement;
@@ -53,7 +53,7 @@ import static org.mockito.Mockito.when;
 public class CatalogMetadataServiceMockTest extends BaseIntegrationTest {
 
     @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
+    private TestRdfMetadataFixtures testMetadataFixtures;
 
     @Mock
     private GenericMetadataRepositoryImpl metadataRepository;
@@ -65,8 +65,8 @@ public class CatalogMetadataServiceMockTest extends BaseIntegrationTest {
     private CatalogMetadataService catalogMetadataService;
 
     @BeforeEach
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
+    public void setUp() throws Exception {
+        MockitoAnnotations.openMocks(this).close();
     }
 
     @Test
@@ -74,7 +74,7 @@ public class CatalogMetadataServiceMockTest extends BaseIntegrationTest {
         // GIVEN: Retrieve catalog from Repository
         Model catalog = testMetadataFixtures.catalog1();
         List<Statement> catalogStatements = new ArrayList<>(catalog);
-        when(metadataRepository.retrieveResource(getUri(catalog))).thenReturn(catalogStatements);
+        when(metadataRepository.find(getUri(catalog))).thenReturn(catalogStatements);
 
         // AND: Retrieve themes from datasets
         IRI theme1 = i("http://localhost/my_theme_1");
@@ -87,11 +87,9 @@ public class CatalogMetadataServiceMockTest extends BaseIntegrationTest {
 
         // THEN:
         List<IRI> themeTaxonomys = getThemeTaxonomies(catalogMetadata);
-        assertThat(themeTaxonomys.size(), is(equalTo(4)));
+        assertThat(themeTaxonomys.size(), is(equalTo(2)));
         assertThat(themeTaxonomys.get(0), is(equalTo(theme1)));
         assertThat(themeTaxonomys.get(1), is(equalTo(theme2_duplicated)));
-        assertThat(themeTaxonomys.get(2), is(equalTo(getThemeTaxonomies(catalog).get(1))));
-        assertThat(themeTaxonomys.get(3), is(equalTo(getThemeTaxonomies(catalog).get(0))));
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceTest.java b/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceTest.java
deleted file mode 100755
index f539f00bee6b88d43fd21a960f07d0fbb596bc2f..0000000000000000000000000000000000000000
--- a/src/test/java/nl/dtls/fairdatapoint/service/metadata/catalog/CatalogMetadataServiceTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 DTL
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package nl.dtls.fairdatapoint.service.metadata.catalog;
-
-import nl.dtls.fairdatapoint.BaseIntegrationTest;
-import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
-import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
-import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
-import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Model;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-
-import java.time.LocalDateTime;
-
-import static java.lang.String.format;
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.*;
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataSetter.*;
-import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
-import static org.junit.jupiter.api.Assertions.*;
-
-public class CatalogMetadataServiceTest extends BaseIntegrationTest {
-
-    @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
-
-    @Autowired
-    @Qualifier("genericMetadataService")
-    private MetadataService genericMetadataService;
-
-    @Autowired
-    @Qualifier("catalogMetadataService")
-    private MetadataService catalogMetadataService;
-
-    @Autowired
-    private ResourceDefinitionFixtures resourceDefinitionFixtures;
-
-    private ResourceDefinition catalogRd;
-
-    @BeforeEach
-    public void setUp() throws Exception {
-        catalogRd = resourceDefinitionFixtures.catalogDefinition();
-    }
-
-    @Test
-    public void retrieveNonExitingMetadata() {
-        assertThrows(ResourceNotFoundException.class, () -> {
-            // GIVEN:
-            IRI repositoryUri = getUri(testMetadataFixtures.repositoryMetadata());
-            IRI catalogUri = i(format("%s/non-existing", repositoryUri));
-
-            // WHEN:
-            catalogMetadataService.retrieve(catalogUri);
-
-            // THEN:
-            // Expect exception
-        });
-    }
-
-    @Test
-    public void specsLink() throws Exception {
-        // GIVEN:
-        Model catalog = testMetadataFixtures.catalog3();
-        catalogMetadataService.store(catalog, getUri(catalog), catalogRd);
-
-        // WHEN:
-        Model metadata = catalogMetadataService.retrieve(getUri(catalog));
-
-        // THEN:
-        assertNotNull(getSpecification(metadata));
-    }
-
-    @Test
-    public void storeAndRetrieve() throws Exception {
-        // GIVEN:
-        Model catalog = testMetadataFixtures.catalog3();
-
-        // WHEN:
-        catalogMetadataService.store(catalog, getUri(catalog), catalogRd);
-
-        // THEN:
-        assertNotNull(catalogMetadataService.retrieve(getUri(catalog)));
-    }
-
-    @Test
-    public void storeWithNoID() throws Exception {
-        // GIVEN:
-        Model catalog = testMetadataFixtures.catalog3();
-        setMetadataIdentifier(catalog, getUri(catalog), null);
-
-        // WHEN:
-        catalogMetadataService.store(catalog, getUri(catalog), catalogRd);
-
-        // THEN:
-        Model result = catalogMetadataService.retrieve(getUri(catalog));
-        assertNotNull(getMetadataIdentifier(result));
-    }
-
-    @Test
-    public void storeWithNoLanguage() throws Exception {
-        // GIVEN:
-        Model catalog = testMetadataFixtures.catalog3();
-        setLanguage(catalog, getUri(catalog), null);
-
-        // WHEN:
-        catalogMetadataService.store(catalog, getUri(catalog), catalogRd);
-
-        // THEN:
-        Model result = catalogMetadataService.retrieve(getUri(catalog));
-        assertNotNull(getLanguage(result));
-    }
-
-    @Test
-    public void storeWithNoLicense() throws Exception {
-        // GIVEN:
-        Model catalog = testMetadataFixtures.catalog3();
-        setLicence(catalog, getUri(catalog), null);
-
-        // WHEN:
-        catalogMetadataService.store(catalog, getUri(catalog), catalogRd);
-
-        // THEN:
-        Model result = catalogMetadataService.retrieve(getUri(catalog));
-        assertNotNull(getLicence(result));
-    }
-
-    @Test
-    public void updateParent() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-        Model catalog = testMetadataFixtures.catalog3();
-
-        // WHEN:
-        catalogMetadataService.store(catalog, getUri(catalog), catalogRd);
-
-        // THEN:
-        Model updatedRepository = genericMetadataService.retrieve(getUri(repository));
-        Model updatedCatalog = catalogMetadataService.retrieve(getUri(catalog));
-        LocalDateTime repositoryModified = getModified(updatedRepository);
-        LocalDateTime catalogModified = getModified(updatedCatalog);
-        assertFalse(repositoryModified.isBefore(catalogModified), "FDP modified is not after Catalog modified");
-    }
-
-}
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/metadata/common/GenericMetadataServiceTest.java b/src/test/java/nl/dtls/fairdatapoint/service/metadata/common/GenericMetadataServiceTest.java
deleted file mode 100755
index 8797ff04c7a73ec863fb3d84831746546e1e3b5e..0000000000000000000000000000000000000000
--- a/src/test/java/nl/dtls/fairdatapoint/service/metadata/common/GenericMetadataServiceTest.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 DTL
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package nl.dtls.fairdatapoint.service.metadata.common;
-
-import nl.dtls.fairdatapoint.BaseIntegrationTest;
-import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
-import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
-import nl.dtls.fairdatapoint.entity.exception.ValidationException;
-import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
-import nl.dtls.fairdatapoint.service.metadata.exception.MetadataServiceException;
-import nl.dtls.fairdatapoint.utils.AuthHelper;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Model;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-
-import java.time.LocalDateTime;
-
-import static java.lang.String.format;
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.*;
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataSetter.*;
-import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
-import static org.junit.jupiter.api.Assertions.*;
-
-public class GenericMetadataServiceTest extends BaseIntegrationTest {
-
-    @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
-
-    @Autowired
-    @Qualifier("genericMetadataService")
-    private MetadataService genericMetadataService;
-
-    @Autowired
-    @Qualifier("catalogMetadataService")
-    private MetadataService catalogMetadataService;
-
-    @Autowired
-    private AuthHelper authHelper;
-
-    @Autowired
-    private ResourceDefinitionFixtures resourceDefinitionFixtures;
-
-    private ResourceDefinition distributionRd;
-
-    @BeforeEach
-    public void before() {
-        authHelper.authenticateAsAlbert();
-        distributionRd = resourceDefinitionFixtures.distributionDefinition();
-    }
-
-    @Test
-    public void retrieveNonExitingMetadata() {
-        assertThrows(ResourceNotFoundException.class, () -> {
-            // GIVEN:
-            IRI repositoryUri = getUri(testMetadataFixtures.repositoryMetadata());
-            IRI datasetUri = i(format("%s/non-existing", repositoryUri));
-
-            // WHEN:
-            genericMetadataService.retrieve(datasetUri);
-
-            // THEN:
-            // Expect exception
-        });
-    }
-
-    @Test
-    public void existenceDatasetMetaDataSpecsLink() throws Exception {
-        // GIVEN:
-        Model distribution = testMetadataFixtures.c1_d1_distribution1();
-
-        // WHEN:
-        genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-        // THEN:
-        Model metadata = genericMetadataService.retrieve(getUri(distribution));
-        assertNotNull(getSpecification(metadata));
-    }
-
-    @Test
-    public void storeAndRetrieve() throws Exception {
-        // GIVEN:
-        Model distribution = testMetadataFixtures.c1_d1_distribution1();
-
-        // WHEN:
-        genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-        // THEN:
-        assertNotNull(genericMetadataService.retrieve(getUri(distribution)));
-    }
-
-    @Test
-    public void storeWithNoParentURI() {
-        assertThrows(MetadataServiceException.class, () -> {
-            // GIVEN:
-            Model distribution = testMetadataFixtures.c1_d1_distribution1();
-            setParent(distribution, getUri(distribution), null);
-
-            // WHEN:
-            genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-            // THEN:
-            // Expect exception
-        });
-    }
-
-    @Test
-    public void storeWithWrongParentURI() {
-        assertThrows(ValidationException.class, () -> {
-            // GIVEN:
-            Model repository = testMetadataFixtures.repositoryMetadata();
-            Model distribution = testMetadataFixtures.c1_d1_distribution1();
-            setParent(distribution, getUri(distribution), getUri(repository));
-
-            // WHEN:
-            genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-            // THEN:
-            // Expect exception
-        });
-    }
-
-    @Test
-    public void storeWithNoID() throws Exception {
-        // GIVEN:
-        Model distribution = testMetadataFixtures.c1_d1_distribution1();
-        setMetadataIdentifier(distribution, getUri(distribution), null);
-
-        // WHEN:
-        genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-        // THEN:
-        Model mdata = genericMetadataService.retrieve(getUri(distribution));
-        assertNotNull(getMetadataIdentifier(mdata));
-    }
-
-    @Test
-    public void storeWithNoLicense() throws Exception {
-        // GIVEN:
-        Model distribution = testMetadataFixtures.c1_d1_distribution1();
-        setLicence(distribution, getUri(distribution), null);
-
-        // WHEN:
-        genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-        // THEN:
-        Model mdata = genericMetadataService.retrieve(getUri(distribution));
-        assertNotNull(getLicence(mdata));
-    }
-
-    @Test
-    public void storeWithNoLanguage() throws Exception {
-        // GIVEN:
-        Model distribution = testMetadataFixtures.c1_d1_distribution1();
-        setLanguage(distribution, getUri(distribution), null);
-
-        // WHEN:
-        genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-        // THEN:
-        Model mdata = genericMetadataService.retrieve(getUri(distribution));
-        assertNotNull(getLanguage(mdata));
-    }
-
-    @Test
-    public void updateParent() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-        Model catalog = testMetadataFixtures.catalog1();
-        Model dataset = testMetadataFixtures.c1_dataset1();
-        Model distribution = testMetadataFixtures.c1_d1_distribution1();
-
-        // WHEN:
-        genericMetadataService.store(distribution, getUri(distribution), distributionRd);
-
-        // THEN:
-        Model updatedRepository = genericMetadataService.retrieve(getUri(repository));
-        Model updatedCatalog = catalogMetadataService.retrieve(getUri(catalog));
-        Model updatedDataset = genericMetadataService.retrieve(getUri(dataset));
-        Model storedDistribution = genericMetadataService.retrieve(getUri(distribution));
-        LocalDateTime repositoryModified = getModified(updatedRepository);
-        LocalDateTime catalogModified = getModified(updatedCatalog);
-        LocalDateTime datasetModified = getModified(updatedDataset);
-        LocalDateTime distributionModified = getModified(storedDistribution);
-        assertFalse(datasetModified.isBefore(distributionModified), "Dataset modified is not after Distribution " +
-                "modified");
-        assertFalse(catalogModified.isBefore(distributionModified), "Catalog modified is not after Dataset modified");
-        assertFalse(repositoryModified.isBefore(distributionModified), "FDP modified is not after Dataset modified");
-    }
-
-}
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataServiceTest.java b/src/test/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe45771b9b38fd60671bedd4e62269b8460556b8
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/service/metadata/generic/GenericMetadataServiceTest.java
@@ -0,0 +1,212 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.metadata.generic;
+
+import nl.dtls.fairdatapoint.BaseIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.entity.exception.ResourceNotFoundException;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
+import nl.dtls.fairdatapoint.utils.AuthHelper;
+import nl.dtls.fairdatapoint.utils.TestRdfMetadataFixtures;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import java.time.OffsetDateTime;
+
+import static java.lang.String.format;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.*;
+import static nl.dtls.fairdatapoint.entity.metadata.MetadataSetter.*;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class GenericMetadataServiceTest extends BaseIntegrationTest {
+
+    @Autowired
+    private TestRdfMetadataFixtures testMetadataFixtures;
+
+    @Autowired
+    @Qualifier("genericMetadataService")
+    private MetadataService genericMetadataService;
+
+    @Autowired
+    @Qualifier("catalogMetadataService")
+    private MetadataService catalogMetadataService;
+
+    @Autowired
+    private AuthHelper authHelper;
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @BeforeEach
+    public void before() {
+        authHelper.authenticateAsAdmin();
+    }
+
+    @Test
+    public void retrieveNonExitingMetadataThrowsError() {
+        // GIVEN:
+        IRI repositoryUri = getUri(testMetadataFixtures.repositoryMetadata());
+        IRI metadataUri = i(format("%s/distribution/non-existing", repositoryUri));
+
+        // WHEN:
+        ResourceNotFoundException exception = assertThrows(
+                ResourceNotFoundException.class,
+                () -> genericMetadataService.retrieve(metadataUri)
+        );
+
+        // THEN:
+        assertThat(exception.getMessage(), is(equalTo(format("No metadata found for the uri '%s'", metadataUri))));
+    }
+
+    @Test
+    public void storeWorks() throws Exception {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model metadata = testMetadataFixtures.c1_d1_distribution1();
+
+        // WHEN:
+        genericMetadataService.store(metadata, getUri(metadata), metadataRd);
+
+        // THEN:
+        Model metadataFromDB = genericMetadataService.retrieve(getUri(metadata));
+        assertNotNull(metadataFromDB);
+    }
+
+    @Test
+    public void storeWithNoParentURIThrowsError() {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model metadata = testMetadataFixtures.c1_d1_distribution1();
+        setParent(metadata, getUri(metadata), null);
+
+        // WHEN:
+        ValidationException exception = assertThrows(
+                ValidationException.class,
+                () -> genericMetadataService.store(metadata, getUri(metadata), metadataRd)
+        );
+
+        // THEN:
+        assertThat(exception.getMessage(), is(equalTo("Metadata has no parent")));
+    }
+
+    @Test
+    public void storeWithWrongParentURIThrowsError() {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model repository = testMetadataFixtures.repositoryMetadata();
+        Model metadata = testMetadataFixtures.c1_d1_distribution1();
+        setParent(metadata, getUri(metadata), getUri(repository));
+
+        // WHEN:
+        ValidationException exception = assertThrows(
+                ValidationException.class,
+                () -> genericMetadataService.store(metadata, getUri(metadata), metadataRd)
+        );
+
+        // THEN:
+        assertThat(exception.getMessage(), is(equalTo("Parent is not of correct type (RD: Repository)")));
+    }
+
+    @Test
+    public void storeWithNoMetadataIdentifier() throws Exception {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model metadata = testMetadataFixtures.c1_d1_distribution1();
+        setMetadataIdentifier(metadata, getUri(metadata), null);
+
+        // WHEN:
+        genericMetadataService.store(metadata, getUri(metadata), metadataRd);
+
+        // THEN:
+        Model metadataFromDB = genericMetadataService.retrieve(getUri(metadata));
+        assertNotNull(getMetadataIdentifier(metadataFromDB));
+    }
+
+    @Test
+    public void storeWithNoLicense() throws Exception {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model metadata = testMetadataFixtures.c1_d1_distribution1();
+        setLicence(metadata, getUri(metadata), null);
+
+        // WHEN:
+        genericMetadataService.store(metadata, getUri(metadata), metadataRd);
+
+        // THEN:
+        Model metadataFromDB = genericMetadataService.retrieve(getUri(metadata));
+        assertNotNull(getLicence(metadataFromDB));
+    }
+
+    @Test
+    public void storeWithNoLanguage() throws Exception {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model metadata = testMetadataFixtures.c1_d1_distribution1();
+        setLanguage(metadata, getUri(metadata), null);
+
+        // WHEN:
+        genericMetadataService.store(metadata, getUri(metadata), metadataRd);
+
+        // THEN:
+        Model metadataFromDB = genericMetadataService.retrieve(getUri(metadata));
+        assertNotNull(getLanguage(metadataFromDB));
+    }
+
+    @Test
+    public void updateParent() throws Exception {
+        // GIVEN:
+        ResourceDefinition metadataRd = resourceDefinitionFixtures.distributionDefinition();
+        Model repository = testMetadataFixtures.repositoryMetadata();
+        Model catalog = testMetadataFixtures.catalog1();
+        Model dataset = testMetadataFixtures.c1_dataset1();
+        Model distribution = testMetadataFixtures.c1_d1_distribution1();
+
+        // WHEN:
+        genericMetadataService.store(distribution, getUri(distribution), metadataRd);
+
+        // THEN:
+        Model updatedRepository = genericMetadataService.retrieve(getUri(repository));
+        Model updatedCatalog = catalogMetadataService.retrieve(getUri(catalog));
+        Model updatedDataset = genericMetadataService.retrieve(getUri(dataset));
+        Model storedDistribution = genericMetadataService.retrieve(getUri(distribution));
+        OffsetDateTime repositoryModified = getModified(updatedRepository);
+        OffsetDateTime catalogModified = getModified(updatedCatalog);
+        OffsetDateTime datasetModified = getModified(updatedDataset);
+        OffsetDateTime distributionModified = getModified(storedDistribution);
+        assertFalse(datasetModified.isBefore(distributionModified), "Dataset modified is not after Distribution " +
+                "modified");
+        assertFalse(catalogModified.isBefore(distributionModified), "Catalog modified is not after Dataset modified");
+        assertFalse(repositoryModified.isBefore(distributionModified), "FDP modified is not after Dataset modified");
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsServiceImplTest.java b/src/test/java/nl/dtls/fairdatapoint/service/metadata/metric/MetricsMetadataServiceTest.java
old mode 100755
new mode 100644
similarity index 55%
rename from src/test/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsServiceImplTest.java
rename to src/test/java/nl/dtls/fairdatapoint/service/metadata/metric/MetricsMetadataServiceTest.java
index 8c095bf437c5da7924ef0758995d5000d67d3b2b..8e1e5b8fa551a0ecd2a71c99120e32bd644e6c58
--- a/src/test/java/nl/dtls/fairdatapoint/service/metadatametrics/FairMetadataMetricsServiceImplTest.java
+++ b/src/test/java/nl/dtls/fairdatapoint/service/metadata/metric/MetricsMetadataServiceTest.java
@@ -25,48 +25,42 @@
  * To change this template file, choose Tools | Templates
  * and open the template in the editor.
  */
-package nl.dtls.fairdatapoint.service.metadatametrics;
+package nl.dtls.fairdatapoint.service.metadata.metric;
 
 import nl.dtls.fairdatapoint.BaseIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.repository.SettingsRepository;
 import nl.dtls.fairdatapoint.entity.metadata.Metric;
-import org.eclipse.rdf4j.model.ValueFactory;
-import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import nl.dtls.fairdatapoint.service.settings.SettingsCache;
+import org.eclipse.rdf4j.model.IRI;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.util.List;
 
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.i;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
 
-// TODO What is purpose of this class
-public class FairMetadataMetricsServiceImplTest extends BaseIntegrationTest {
+public class MetricsMetadataServiceTest extends BaseIntegrationTest {
 
-    private final ValueFactory valueFactory = SimpleValueFactory.getInstance();
     @Autowired
-    private FairMetadataMetricsServiceImpl fmMetricsServiceImpl;
+    private MetricsMetadataService metricsMetadataService;
 
-    /**
-     * Test getMetrics with null uri, this test is excepted to throw error
-     */
     @Test
-    public void nullMetadataUri() {
-        assertThrows(NullPointerException.class, () -> {
-            fmMetricsServiceImpl.getMetrics(null);
-        });
-    }
+    public void generateMetricsWorks() {
+        // GIVEN:
+        IRI entityUri = i("http://localhost");
 
-    /**
-     * This test is excepted to pass
-     */
-    @Test
-    public void validMetadataUri() {
-//        Map<String, String> metadataMetrics = new HashMap<>();
-//        metadataMetrics.put("https://purl.org/fair-metrics/FM_F1A", "http://example.com/f1a");
-//        fmMetricsServiceImpl.setMetadataMetrics(metadataMetrics);
+        // WHEN:
+        List<Metric> result = metricsMetadataService.generateMetrics(entityUri);
 
-        List<Metric> m = fmMetricsServiceImpl.getMetrics(valueFactory.createIRI("http://localhost"));
-        assertTrue(m.size() > 0);
+        // THEN:
+        assertThat(result.size(), is(equalTo(2)));
+        assertThat(result.get(0).getValue(), is(equalTo(i("https://www.ietf.org/rfc/rfc3986.txt"))));
+        assertThat(result.get(0).getMetricType(), is(equalTo(i("https://www.ietf.org/rfc/rfc3986.txt"))));
+        assertThat(result.get(1).getValue(), is(equalTo(i("https://www.wikidata.org/wiki/Q8777"))));
+        assertThat(result.get(1).getMetricType(), is(equalTo(i("https://www.wikidata.org/wiki/Q8777"))));
     }
 
 }
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/metadata/repository/RepositoryMetadataServiceTest.java b/src/test/java/nl/dtls/fairdatapoint/service/metadata/repository/RepositoryMetadataServiceTest.java
deleted file mode 100755
index 41ae555b6618ad7eb9db9301bd1f379ad42c73b5..0000000000000000000000000000000000000000
--- a/src/test/java/nl/dtls/fairdatapoint/service/metadata/repository/RepositoryMetadataServiceTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 DTL
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package nl.dtls.fairdatapoint.service.metadata.repository;
-
-import nl.dtls.fairdatapoint.BaseIntegrationTest;
-import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
-import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
-import nl.dtls.fairdatapoint.service.metadata.common.MetadataService;
-import nl.dtls.fairdatapoint.utils.AuthHelper;
-import nl.dtls.fairdatapoint.utils.TestMetadataFixtures;
-import org.eclipse.rdf4j.model.IRI;
-import org.eclipse.rdf4j.model.Literal;
-import org.eclipse.rdf4j.model.Model;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.*;
-import static nl.dtls.fairdatapoint.entity.metadata.MetadataSetter.*;
-import static nl.dtls.fairdatapoint.util.ValueFactoryHelper.l;
-import static org.junit.jupiter.api.Assertions.*;
-
-public class RepositoryMetadataServiceTest extends BaseIntegrationTest {
-
-    @Autowired
-    private TestMetadataFixtures testMetadataFixtures;
-
-    @Autowired
-    private AuthHelper authHelper;
-
-    @Autowired
-    @Qualifier("genericMetadataService")
-    private MetadataService genericMetadataService;
-
-    @Autowired
-    private ResourceDefinitionFixtures resourceDefinitionFixtures;
-
-    private ResourceDefinition repositoryRd;
-
-    @BeforeEach
-    public void before() {
-        authHelper.authenticateAsAlbert();
-        repositoryRd = resourceDefinitionFixtures.repositoryDefinition();
-    }
-
-
-    @Test
-    public void storeAndRetrieve() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-
-        // WHEN:
-        genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-        // THEN:
-        assertNotNull(genericMetadataService.retrieve(getUri(repository)));
-    }
-
-    @Test
-    public void storeWithNoTitle() {
-        assertThrows(Exception.class, () -> {
-            // GIVEN:
-            Model repository = testMetadataFixtures.repositoryMetadata();
-            setTitle(repository, getUri(repository), null);
-
-            // WHEN:
-            genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-            // THEN:
-            // Expect exception
-        });
-    }
-
-    @Test
-    public void storeWithNoID() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-        setMetadataIdentifier(repository, getUri(repository), null);
-
-        // WHEN:
-        genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-        // THEN:
-        Model result = genericMetadataService.retrieve(getUri(repository));
-        assertNotNull(getMetadataIdentifier(result));
-    }
-
-    @Test
-    public void storeWithNoLanguage() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-        setLanguage(repository, getUri(repository), null);
-
-        // WHEN:
-        genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-        // THEN:
-        Model result = genericMetadataService.retrieve(getUri(repository));
-        assertNotNull(getLanguage(result));
-    }
-
-    @Test
-    public void storeWithNoLicense() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-        setLicence(repository, getUri(repository), null);
-
-        // WHEN:
-        genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-        // THEN:
-        Model result = genericMetadataService.retrieve(getUri(repository));
-        assertNotNull(getLicence(result));
-    }
-
-    @Test
-    public void update() throws Exception {
-        // GIVEN: Authenticate due to perform changes
-        authHelper.authenticateAsAdmin();
-
-        // AND: Prepare data
-        Model repository = testMetadataFixtures.repositoryMetadata();
-        genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-        // WHEN:
-        Literal title = l("New FDP title");
-        setTitle(repository, getUri(repository), title);
-        genericMetadataService.update(repository, getUri(repository), repositoryRd);
-
-        // THEN:
-        Model result = genericMetadataService.retrieve(getUri(repository));
-        assertEquals(title, getTitle(result));
-    }
-
-    @Test
-    public void nullFDPURI() {
-        assertThrows(NullPointerException.class, () -> {
-            // WHEN:
-            genericMetadataService.retrieve((IRI) null);
-
-            // THEN:
-            // Expect exception
-        });
-    }
-
-    @Test
-    public void specsLink() throws Exception {
-        // GIVEN:
-        Model repository = testMetadataFixtures.repositoryMetadata();
-
-        // WHEN:
-        genericMetadataService.store(repository, getUri(repository), repositoryRd);
-
-        // THEN:
-        Model result = genericMetadataService.retrieve(getUri(repository));
-        assertNotNull(getSpecification(result));
-    }
-
-
-}
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionCacheTest.java b/src/test/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionCacheTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d18ed54ec2d4aa76fa4a4737d2c80069a4abb768
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionCacheTest.java
@@ -0,0 +1,75 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.resource;
+
+import nl.dtls.fairdatapoint.BaseIntegrationTest;
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+public class ResourceDefinitionCacheTest extends BaseIntegrationTest {
+
+    @Autowired
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @Autowired
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    @Autowired
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+
+    @Test
+    public void computeCacheWorks() {
+        // GIVEN: Resource definitions
+        ResourceDefinition rdRepository = resourceDefinitionFixtures.repositoryDefinition();
+        ResourceDefinition rdCatalog = resourceDefinitionFixtures.catalogDefinition();
+        ResourceDefinition rdDataset = resourceDefinitionFixtures.datasetDefinition();
+        ResourceDefinition rdDistribution = resourceDefinitionFixtures.distributionDefinition();
+
+        // WHEN:
+        resourceDefinitionCache.computeCache();
+
+        // THEN: caches by UUID
+        assertThat(resourceDefinitionCache.getByUuid(rdRepository.getUuid()).getName(), is(equalTo(rdRepository.getName())));
+        assertThat(resourceDefinitionCache.getByUuid(rdCatalog.getUuid()).getName(), is(equalTo(rdCatalog.getName())));
+        assertThat(resourceDefinitionCache.getByUuid(rdDataset.getUuid()).getName(), is(equalTo(rdDataset.getName())));
+        assertThat(resourceDefinitionCache.getByUuid(rdDistribution.getUuid()).getName(), is(equalTo(rdDistribution.getName())));
+
+        // AND: caches parents
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdRepository.getUuid()).isEmpty(), is(true));
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdCatalog.getUuid()).size(), is(equalTo(1)));
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdCatalog.getUuid()).stream().toList().get(0).getUuid(), is(equalTo(rdRepository.getUuid())));
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdDataset.getUuid()).size(), is(equalTo(1)));
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdDataset.getUuid()).stream().toList().get(0).getUuid(), is(equalTo(rdCatalog.getUuid())));
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdDistribution.getUuid()).size(), is(equalTo(1)));
+        assertThat(resourceDefinitionCache.getParentsByUuid(rdDistribution.getUuid()).stream().toList().get(0).getUuid(), is(equalTo(rdDataset.getUuid())));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionValidatorTest.java b/src/test/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..06af988c854a0e8394602486eed79a288dab866d
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/service/resource/ResourceDefinitionValidatorTest.java
@@ -0,0 +1,193 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.service.resource;
+
+import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
+import nl.dtls.fairdatapoint.database.mongo.repository.ResourceDefinitionRepository;
+import nl.dtls.fairdatapoint.entity.exception.ValidationException;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
+import nl.dtls.fairdatapoint.entity.resource.ResourceDefinitionChild;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.validation.BindException;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class ResourceDefinitionValidatorTest {
+
+    @Mock
+    private ResourceDefinitionRepository resourceDefinitionRepository;
+
+    @Mock
+    private ResourceDefinitionCache resourceDefinitionCache;
+
+    @InjectMocks
+    private ResourceDefinitionValidator resourceDefinitionValidator;
+
+    @InjectMocks
+    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+
+    @Test
+    public void nameUniqueness() throws BindException {
+        // GIVEN: Prepare reqDto
+        ResourceDefinition reqDto = resourceDefinitionFixtures.repositoryDefinition();
+        reqDto.setChildren(List.of());
+
+        // AND: Prepare database
+        when(resourceDefinitionRepository.findByName(reqDto.getName()))
+                .thenReturn(Optional.of(resourceDefinitionFixtures.repositoryDefinition()));
+
+        // WHEN:
+        resourceDefinitionValidator.validate(reqDto);
+
+        // THEN:
+        // Nothing to check
+    }
+
+    @Test
+    public void nameUniquenessBreach() {
+        // GIVEN: Prepare reqDto
+        ResourceDefinition reqDto = resourceDefinitionFixtures.ontologyDefinition();
+
+        // AND: Prepare database
+        when(resourceDefinitionRepository.findByName(reqDto.getName()))
+                .thenReturn(Optional.of(resourceDefinitionFixtures.repositoryDefinition()));
+
+        // WHEN:
+        BindException exception = assertThrows(
+                BindException.class,
+                () -> resourceDefinitionValidator.validate(reqDto)
+        );
+
+        // THEN:
+        assertThat(exception.getBindingResult().getFieldError("name"), is(notNullValue()));
+    }
+
+    @Test
+    public void urlPrefixUniqueness() throws BindException {
+        // GIVEN: Prepare reqDto
+        ResourceDefinition reqDto = resourceDefinitionFixtures.repositoryDefinition();
+        reqDto.setChildren(List.of());
+
+        // AND: Prepare database
+        when(resourceDefinitionRepository.findByName(reqDto.getName()))
+                .thenReturn(Optional.empty());
+        when(resourceDefinitionRepository.findByUrlPrefix(reqDto.getUrlPrefix()))
+                .thenReturn(Optional.of(resourceDefinitionFixtures.repositoryDefinition()));
+
+        // WHEN:
+        resourceDefinitionValidator.validate(reqDto);
+
+        // THEN:
+        // Nothing to check
+    }
+
+    @Test
+    public void urlPrefixUniquenessBreach() {
+        // GIVEN: Prepare reqDto
+        ResourceDefinition reqDto = resourceDefinitionFixtures.ontologyDefinition();
+
+        // AND: Prepare database
+        when(resourceDefinitionRepository.findByName(reqDto.getName()))
+                .thenReturn(Optional.empty());
+        when(resourceDefinitionRepository.findByUrlPrefix(reqDto.getUrlPrefix()))
+                .thenReturn(Optional.of(resourceDefinitionFixtures.repositoryDefinition()));
+
+        // WHEN:
+        BindException exception = assertThrows(
+                BindException.class,
+                () -> resourceDefinitionValidator.validate(reqDto)
+        );
+
+        // THEN:
+        assertThat(exception.getBindingResult().getFieldError("urlPrefix"), is(notNullValue()));
+    }
+
+    @Test
+    public void nonExistingChild() {
+        // GIVEN: Prepare reqDto
+        ResourceDefinition reqDto = resourceDefinitionFixtures.ontologyDefinition();
+        ResourceDefinitionChild child = new ResourceDefinitionChild("nonExistingChild", "", null);
+        reqDto.setChildren(List.of(child));
+
+        // AND: Prepare database
+        when(resourceDefinitionRepository.findByName(reqDto.getName()))
+                .thenReturn(Optional.empty());
+        when(resourceDefinitionRepository.findByUrlPrefix(reqDto.getUrlPrefix()))
+                .thenReturn(Optional.empty());
+        when(resourceDefinitionCache.getByUuid(child.getResourceDefinitionUuid()))
+                .thenReturn(null);
+
+        // WHEN:
+        ValidationException exception = assertThrows(
+                ValidationException.class,
+                () -> resourceDefinitionValidator.validate(reqDto)
+        );
+
+        // THEN:
+        assertThat(exception.getMessage(), is(equalTo("Child doesn't exist")));
+    }
+
+    @Test
+    public void existingDependencyCycle() {
+        // GIVEN: Prepare reqDto and resource definitions
+        ResourceDefinition rdRepository = resourceDefinitionFixtures.repositoryDefinition();
+        ResourceDefinition reqDto = resourceDefinitionFixtures.catalogDefinition();
+        ResourceDefinition rdDataset = resourceDefinitionFixtures.datasetDefinition();
+
+        ResourceDefinitionChild rdDatasetChild = new ResourceDefinitionChild(rdRepository.getUuid(), "", null);
+        rdDataset.setChildren(List.of(rdDatasetChild));
+
+        // AND: Prepare database
+        when(resourceDefinitionRepository.findByName(reqDto.getName()))
+                .thenReturn(Optional.empty());
+        when(resourceDefinitionRepository.findByUrlPrefix(reqDto.getUrlPrefix()))
+                .thenReturn(Optional.empty());
+        when(resourceDefinitionCache.getByUuid(rdRepository.getUuid()))
+                .thenReturn(rdRepository);
+        when(resourceDefinitionCache.getByUuid(rdDataset.getUuid()))
+                .thenReturn(rdDataset);
+
+        // WHEN:
+        ValidationException exception = assertThrows(
+                ValidationException.class,
+                () -> resourceDefinitionValidator.validate(reqDto)
+        );
+
+        // THEN:
+        assertThat(exception.getMessage(), is(equalTo("Detect dependency cycle through child")));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/utils/CustomPageImpl.java b/src/test/java/nl/dtls/fairdatapoint/utils/CustomPageImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f525e9439ff964d381d816eef04c01209b1a49b
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/utils/CustomPageImpl.java
@@ -0,0 +1,62 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.utils;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CustomPageImpl<T> {
+
+    private int totalPages;
+    private long totalElements;
+    private boolean first;
+    private CustomSort sort;
+    private CustomPageable pageable;
+    private int number;
+    private int numberOfElements;
+    private boolean last;
+    private int size;
+    private List<T> content;
+    private boolean empty;
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class CustomPageable {
+        private int page;
+        private int size;
+        private CustomSort sort;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class CustomSort {
+        private boolean sorted;
+        private boolean unsorted;
+        private boolean empty;
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/utils/HttpUtilTest.java b/src/test/java/nl/dtls/fairdatapoint/utils/HttpUtilTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b53ed04845147a1ec1970a120f68159310638c2b
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/utils/HttpUtilTest.java
@@ -0,0 +1,98 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.utils;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import javax.servlet.http.HttpServletRequest;
+
+import static nl.dtls.fairdatapoint.util.HttpUtil.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HttpUtilTest {
+
+    @ParameterizedTest
+    @CsvSource({
+            "http://fairdatapoint.com/,http://purl.org/fairdatapoint/test",
+            "http://fairdatapoint.com/catalog/catalog-1,http://purl.org/fairdatapoint/test/catalog/catalog-1",
+            "https://fairdatapoint.com/catalog/catalog-1/,http://purl.org/fairdatapoint/test/catalog/catalog-1",
+    })
+    public void getRequestURLTest(String url, String expected) {
+        // GIVEN: Prepare request
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getRequestURL()).thenReturn(new StringBuffer(url));
+        String persistentUrl = "http://purl.org/fairdatapoint/test";
+
+        // WHEN:
+        String result = getRequestURL(request, persistentUrl);
+
+        // THEN:
+        assertThat(result, is(equalTo(expected)));
+    }
+
+    @Test
+    public void generateNewIRITest() {
+        // GIVEN: Prepare request
+        String url = "http://fairdatapoint.com/";
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getRequestURL()).thenReturn(new StringBuffer(url));
+        String persistentUrl = "http://purl.org/fairdatapoint/test";
+
+        // WHEN:
+        IRI result = generateNewIRI(request, persistentUrl);
+
+        // THEN:
+        assertThat(result.stringValue().length(), is(equalTo(71)));
+        assertThat(result.stringValue().startsWith(persistentUrl), is(equalTo(true)));
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+            "http://example.com/,http://example.com",
+            "http://example.com,http://example.com",
+            "example.com/,example.com",
+            "example.com/foo,example.com/foo",
+            "example.com/foo/,example.com/foo",
+    })
+    public void removeLastSlashTest(String url, String expected) {
+        assertThat(removeLastSlash(url), is(equalTo(expected)));
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+            "http://example.com,example.com",
+            "https://example.com,example.com",
+            "example.com,example.com",
+    })
+    public void removeProtocolTest(String url, String expected) {
+        assertThat(removeProtocol(url), is(equalTo(expected)));
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/utils/TestIndexEntryFixtures.java b/src/test/java/nl/dtls/fairdatapoint/utils/TestIndexEntryFixtures.java
new file mode 100644
index 0000000000000000000000000000000000000000..3216d75a00076835aed98a658c67796ca50ddbb6
--- /dev/null
+++ b/src/test/java/nl/dtls/fairdatapoint/utils/TestIndexEntryFixtures.java
@@ -0,0 +1,70 @@
+/**
+ * The MIT License
+ * Copyright © 2017 DTL
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package nl.dtls.fairdatapoint.utils;
+
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntry;
+import nl.dtls.fairdatapoint.entity.index.entry.IndexEntryState;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+public class TestIndexEntryFixtures {
+
+    private static IndexEntry newIndexEntry(String uuid, String clientUrl, Instant timestamp) {
+        IndexEntry indexEntry = new IndexEntry();
+        indexEntry.setUuid(uuid);
+        indexEntry.setClientUrl(clientUrl);
+        indexEntry.setModificationTime(timestamp);
+        indexEntry.setRegistrationTime(timestamp);
+        indexEntry.setState(IndexEntryState.Invalid);
+        return indexEntry;
+    }
+
+    public static IndexEntry entryExample() {
+        return newIndexEntry("7663c0c2-2b9d-4787-968d-d284ff3fc5bd", "http://example.com", Instant.now());
+    }
+
+    public static List<IndexEntry> entriesFew() {
+        Instant ref = Instant.now();
+        return Arrays.asList(
+                newIndexEntry("09200532-18b4-4721-86dd-fbfa13ec78c3", "http://example.com", ref),
+                newIndexEntry("b6cfa934-dc67-4b88-b8f9-c63448c8272c", "http://test.com", ref.minusSeconds(1)),
+                newIndexEntry("da9ddfb8-6fdb-41b1-889e-387c8cbafc39", "http://localhost", ref.minusSeconds(2))
+        );
+    }
+
+    public static List<IndexEntry> entriesN(long n) {
+        ArrayList<IndexEntry> entries = new ArrayList<>();
+        Instant ref = Instant.now();
+        for (int i = 0; i < n; i++) {
+            Instant entryTime = ref.minusSeconds(i);
+            entries.add(newIndexEntry(UUID.randomUUID().toString(), "http://example" + i + ".com",
+                    entryTime));
+        }
+        return entries;
+    }
+
+}
diff --git a/src/test/java/nl/dtls/fairdatapoint/utils/TestMetadataFixtures.java b/src/test/java/nl/dtls/fairdatapoint/utils/TestRdfMetadataFixtures.java
old mode 100755
new mode 100644
similarity index 85%
rename from src/test/java/nl/dtls/fairdatapoint/utils/TestMetadataFixtures.java
rename to src/test/java/nl/dtls/fairdatapoint/utils/TestRdfMetadataFixtures.java
index f8abaeb5d2cd2f7bd82566f791c5c4633ef86382..424a63265dcf02b30f1133bca23a8f5138c5e6ef
--- a/src/test/java/nl/dtls/fairdatapoint/utils/TestMetadataFixtures.java
+++ b/src/test/java/nl/dtls/fairdatapoint/utils/TestRdfMetadataFixtures.java
@@ -23,7 +23,8 @@
 package nl.dtls.fairdatapoint.utils;
 
 import nl.dtls.fairdatapoint.database.mongo.migration.development.resource.data.ResourceDefinitionFixtures;
-import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.data.MetadataFixtures;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.data.RdfMetadataFixtures;
+import nl.dtls.fairdatapoint.database.rdf.migration.development.metadata.factory.MetadataFactory;
 import nl.dtls.fairdatapoint.entity.resource.ResourceDefinition;
 import nl.dtls.fairdatapoint.service.metadata.enhance.MetadataEnhancer;
 import org.eclipse.rdf4j.model.Model;
@@ -34,19 +35,26 @@ import org.springframework.stereotype.Service;
 import static nl.dtls.fairdatapoint.entity.metadata.MetadataGetter.getUri;
 
 @Service
-public class TestMetadataFixtures extends MetadataFixtures {
-
-    @Autowired
-    @Qualifier("persistentUrl")
-    private String persistentUrl;
+public class TestRdfMetadataFixtures extends RdfMetadataFixtures {
 
     public String alternativePersistentUrl = "https://lorentz.fair-dtls.surf-hosted.nl/fdp";
 
-    @Autowired
-    private MetadataEnhancer metadataEnhancer;
+    private final String persistentUrl;
+
+    private final MetadataEnhancer metadataEnhancer;
+
+    private final ResourceDefinitionFixtures resourceDefinitionFixtures;
 
     @Autowired
-    private ResourceDefinitionFixtures resourceDefinitionFixtures;
+    public TestRdfMetadataFixtures(MetadataFactory metadataFactory,
+                                   @Qualifier("persistentUrl") String persistentUrl,
+                                   MetadataEnhancer metadataEnhancer,
+                                   ResourceDefinitionFixtures resourceDefinitionFixtures) {
+        super(metadataFactory);
+        this.persistentUrl = persistentUrl;
+        this.metadataEnhancer = metadataEnhancer;
+        this.resourceDefinitionFixtures = resourceDefinitionFixtures;
+    }
 
     public Model repositoryMetadata() {
         Model metadata = super.repositoryMetadata(persistentUrl);
diff --git a/version b/version
index ec429617da2180a51560c0b53f351b9d969eb6ed..50aa215e614b2585a5a7332b0fff427b7a80b520 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-v27
\ No newline at end of file
+v28
\ No newline at end of file