You will be provided with a partial code base and an issue statement explaining a problem to resolve.
<issue>
DiagnosisKeyDistributionRunner deletes exposure configuration
``DiagnosisKeyDistributionRunner`` deletes the files written by ``ExposureConfigurationDistributionRunner`` before they are published.

</issue>

<code>
[start of README.md]
1 <h1 align="center">
2     Corona Warn App - Server
3 </h1>
4 
5 <p align="center">
6     <a href="https://github.com/Exposure-Notification-App/ena-documentation/commits/" title="Last Commit"><img src="https://img.shields.io/github/last-commit/corona-warn-app/cwa-server?style=flat"></a>
7     <a href="https://github.com/Exposure-Notification-App/ena-documentation/issues" title="Open Issues"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-server?style=flat"></a>
8     <a href="https://circleci.com/gh/corona-warn-app/cwa-server" title="Build Status"><img src="https://circleci.com/gh/corona-warn-app/cwa-server.svg?style=flat&circle-token=a7294b977bb9ea2c2d53ff62c9aa442670e19b59"></a>
9     <a href="https://github.com/corona-warn-app/cwa-server/blob/master/LICENSE" title="License"><img src="https://img.shields.io/badge/License-Apache%202.0-green.svg?style=flat"></a>
10 </p>
11 
12 <p align="center">
13   <a href="#service-apis">Service APIs</a> •
14   <a href="#development">Development</a> •
15   <a href="#architecture--documentation">Documentation</a> •
16   <a href="#contributing">Contributing</a> •
17   <a href="#support--feedback">Support</a> •
18   <a href="https://github.com/corona-warn-app/cwa-admin/releases">Changelog</a>
19 </p>
20 
21 This project has the goal to develop the official Corona-Warn-App for Germany based on the Exposure Notification API by [Apple](https://www.apple.com/covid19/contacttracing/) and [Google](https://www.google.com/covid19/exposurenotifications/).  The apps (for both iOS and Android) will collect anonymous data from nearby mobile phones using Bluetooth technology. The data will be stored locally on each device, preventing authorities’ access and control over tracing data. This repository contains the **implementation of the key server** for the Corona-Warn-App. This implementation is **work in progress** and contains alpha-quality code only.
22 
23 ## Service APIs
24 
25 Service      | OpenAPI Specification
26 -------------|-------------
27 Submission Service        | https://github.com/corona-warn-app/cwa-server/raw/master/services/submission/api_v1.json
28 Distribution Service      | https://github.com/corona-warn-app/cwa-server/raw/master/services/distribution/api_v1.json
29 
30 
31 ## Development
32 
33 ### Setup
34 
35 To setup this project locally on your machine, we recommend to install the following prerequisites:
36   - Java OpenJDK 11
37   - Maven 3.6
38   - Docker if you would like to build Docker images
39   - Postgres if you would like to connect a persistent storage. If no postgres connection is specified an in-memory HSQLDB will be provided.
40 
41 ### Build
42 
43 After you checked out the repository run ```mvn install``` in your base directory to build the project.
44 
45 ### Run
46 
47 Navigate to the service you would like to start and run the spring-boot:run target. By default the HSQLDB will be used and after the Spring Boot application started the endpoint will be available on your local port 8080. As an example if you would like to start the submission service run:
48 ```
49   cd services/submission/
50   mvn spring-boot:run
51 ```
52 
53 If you want to use a postgres DB instead please use the postgres profile when starting the application:
54 
55 ```
56   cd services/submission/
57   mvn spring-boot:run -Dspring-boot.run.profiles=postgres
58 ```
59 
60 In order to enable S3 integration, you will need the following vars in your env:
61 
62 Var | Description
63 ----|----------------
64 AWS_ACCESS_KEY_ID | The access key
65 AWS_SECRET_ACCESS_KEY | The secret access key
66 cwa.objectstore.endpoint | The S3 endpoint
67 cwa.objectstore.bucket | The S3 bucket name
68 
69 ### Build and run Docker Compose (full stack)
70 To start:
71 - Run `docker-compose up`
72 - After the initial build and startup, an initial "distribution" will run
73 
74 To start additional "distribution" runs:
75 - Run `docker-compose start distribution`
76 
77 ### Build and run single Docker Images
78 
79 First download and install [Docker](https://www.docker.com/products/docker-desktop).
80 
81 If you want to build and run a docker image of a service you can use the prepared script in the respective service directory.
82 To build and run the distribution service:
83 ```
84 ./services/distribution/build_and_run.sh
85 ```
86 
87 To build and run the submission service:
88 ```
89 ./services/submission/build_and_run.sh
90 ```
91 The submission service will then be available locally on port 8080.
92 
93 ## Debugging 
94 
95 You may run the application with the spring profile `dev` to enable the `DEBUG` log-level.
96 
97 ```
98 mvn spring-boot:run -Dspring-boot.run.profiles=postgres,dev
99 ```
100 
101 To be able to set breakpoints (e.g. in IntelliJ), it may be necessary to use the parameter `-Dspring-boot.run.fork=false`. 
102 
103 
104 ## Known Issues
105 
106 There are no known issues.
107 
108 ## Architecture & Documentation
109 
110 The full documentation for the Corona-Warn-App is in the [cwa-documentation](https://github.com/corona-warn-app/cwa-documentation) repository. Please refer to this repository for technical documents, UI/UX specifications, architectures, and whitepapers of this implementation.
111 
112 ## Support & Feedback
113 
114 | Type                     | Channel                                                |
115 | ------------------------ | ------------------------------------------------------ |
116 | **General Discussion**   | <a href="https://github.com/corona-warn-app/cwa-documentation/issues/new/choose" title="General Discussion"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-documentation/question.svg?style=flat-square"></a> </a>   |
117 | **Concept Feedback**    | <a href="https://github.com/corona-warn-app/cwa-documentation/issues/new/choose" title="Open Concept Feedback"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-documentation/concept-extension.svg?style=flat-square"></a>  |
118 | **iOS App Issue**    | <a href="https://github.com/corona-warn-app/cwa-app-ios/issues/new/choose" title="Open iOS Suggestion"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-app-ios/ios-app.svg?style=flat-square"></a>  |
119 | **Android App Issue**    | <a href="https://github.com/corona-warn-app/cwa-app-android/issues/new/choose" title="Open Android Issue"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-app-android/android-app.svg?style=flat-square"></a>  |
120 | **Backend Issue**    | <a href="https://github.com/corona-warn-app/cwa-server/issues/new/choose" title="Open Backend Issue"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-server/backend.svg?style=flat-square"></a>  |
121 | **Other Requests**    | <a href="mailto:corona-warn-app.opensource@sap.com" title="Email CWD Team"><img src="https://img.shields.io/badge/email-CWD%20team-green?logo=mail.ru&style=flat-square&logoColor=white"></a>   |
122 
123 ## Contributing
124 
125 Contributions and feedback are encouraged and always welcome. Please see our [Contribution Guidelines](./CONTRIBUTING.md) for details on how to contribute, the project structure and additional details you need to know to work with us. By participating in this project, you agree to abide by its [Code of Conduct](./CODE_OF_CONDUCT.md).
126 
127 ## Contributors
128 
129 The German government has asked SAP and Deutsche Telekom to develop the Corona-Warn-App. Deutsche Telekom is providing the infrastructure technology and will operate and run the backend for the app in a safe, scalable, and stable manner. SAP is responsible for the development of the app development and the exposure notification backend. Therefore, development teams of SAP and T-Systems are contributing to this project. At the same time, our commitment to open source means that we are enabling -and encouraging- all interested parties to contribute and become part of its developer community. 
130 
131 ## Repositories
132 
133 | Repository          | Description                                                           |
134 | ------------------- | --------------------------------------------------------------------- |
135 | [cwa-documentation] | Project overview, general documentation, and white papers.            |
136 | [cwa-app-ios]       | Native iOS app using the Apple/Google exposure notification API.      |
137 | [cwa-app-android]   | Native Android app using the Apple/Google exposure notification API.  |
138 | [cwa-server]        | Backend implementation for the Apple/Google exposure notification API.|
139 
140 [cwa-documentation]: https://github.com/corona-warn-app/cwa-documentation
141 [cwa-app-ios]: https://github.com/corona-warn-app/cwa-app-ios
142 [cwa-app-android]: https://github.com/corona-warn-app/cwa-app-android
143 [cwa-server]: https://github.com/corona-warn-app/cwa-server
144 
145 ---
146 
147 This project is licensed under the **Apache-2.0** license. For more information, see the [LICENSE](./LICENSE) file.
148 
[end of README.md]
[start of /dev/null]
1 
[end of /dev/null]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/crypto/CryptoProvider.java]
1 package app.coronawarn.server.services.distribution.crypto;
2 
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.security.PrivateKey;
8 import java.security.Security;
9 import java.security.cert.Certificate;
10 import java.security.cert.CertificateException;
11 import java.security.cert.CertificateFactory;
12 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
13 import org.bouncycastle.jce.provider.BouncyCastleProvider;
14 import org.bouncycastle.openssl.PEMParser;
15 import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.beans.factory.annotation.Value;
20 import org.springframework.core.io.Resource;
21 import org.springframework.core.io.ResourceLoader;
22 import org.springframework.stereotype.Component;
23 
24 /**
25  * Wrapper class for a {@link CryptoProvider#getPrivateKey() private key} and a {@link
26  * CryptoProvider#getCertificate()} certificate} from the application properties.
27  */
28 @Component
29 public class CryptoProvider {
30 
31   private static final Logger logger = LoggerFactory.getLogger(CryptoProvider.class);
32 
33   @Value("${app.coronawarn.server.services.distribution.paths.privatekey}")
34   private String privateKeyPath;
35 
36   @Value("${app.coronawarn.server.services.distribution.paths.certificate}")
37   private String certificatePath;
38 
39   @Autowired
40   private ResourceLoader resourceLoader;
41 
42   private PrivateKey privateKey;
43   private Certificate certificate;
44 
45   /**
46    * Creates a CryptoProvider, using {@link BouncyCastleProvider}.
47    */
48   public CryptoProvider() {
49     Security.addProvider(new BouncyCastleProvider());
50   }
51 
52   private static PrivateKey getPrivateKeyFromStream(final InputStream privateKeyStream)
53       throws IOException {
54     PEMParser pemParser = new PEMParser(new InputStreamReader(privateKeyStream));
55     PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
56     return new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo);
57   }
58 
59   private static Certificate getCertificateFromStream(final InputStream certificateStream)
60       throws IOException, CertificateException {
61     return getCertificateFromBytes(certificateStream.readAllBytes());
62   }
63 
64   private static Certificate getCertificateFromBytes(final byte[] bytes)
65       throws CertificateException {
66     CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
67     InputStream certificateByteStream = new ByteArrayInputStream(bytes);
68     return certificateFactory.generateCertificate(certificateByteStream);
69   }
70 
71   /**
72    * Reads and returns the {@link PrivateKey} configured in the application properties.
73    */
74   public PrivateKey getPrivateKey() {
75     if (this.privateKey == null) {
76       Resource privateKeyResource = resourceLoader.getResource(privateKeyPath);
77       try (InputStream privateKeyStream = privateKeyResource.getInputStream()) {
78         this.privateKey = getPrivateKeyFromStream(privateKeyStream);
79       } catch (IOException e) {
80         logger.error("Failed to load private key from {}", privateKeyPath, e);
81         throw new RuntimeException(e);
82       }
83     }
84     return privateKey;
85   }
86 
87   /**
88    * Reads and returns the {@link Certificate} configured in the application properties.
89    */
90   public Certificate getCertificate() {
91     if (this.certificate == null) {
92       Resource certResource = resourceLoader.getResource(certificatePath);
93       try (InputStream certStream = certResource.getInputStream()) {
94         this.certificate = getCertificateFromStream(certStream);
95       } catch (IOException | CertificateException e) {
96         logger.error("Failed to load certificate from {}", certificatePath, e);
97         throw new RuntimeException(e);
98       }
99     }
100     return certificate;
101   }
102 }
103 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/crypto/CryptoProvider.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory.decorator.DateAggregatingDecorator;
6 import app.coronawarn.server.services.distribution.structure.directory.Directory;
7 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
8 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
9 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
10 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
11 import java.time.LocalDate;
12 import java.util.Collection;
13 import java.util.Set;
14 
15 public class DiagnosisKeysCountryDirectoryImpl extends IndexDirectoryImpl<String> {
16 
17   private static final String COUNTRY_DIRECTORY = "country";
18   private static final String COUNTRY = "DE";
19 
20   private Collection<DiagnosisKey> diagnosisKeys;
21   private CryptoProvider cryptoProvider;
22 
23   public DiagnosisKeysCountryDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
24       CryptoProvider cryptoProvider) {
25     super(COUNTRY_DIRECTORY, __ -> Set.of(COUNTRY), Object::toString);
26     this.diagnosisKeys = diagnosisKeys;
27     this.cryptoProvider = cryptoProvider;
28   }
29 
30   @Override
31   public void prepare(ImmutableStack<Object> indices) {
32     this.addDirectoryToAll(__ -> {
33       IndexDirectory<LocalDate> dateDirectory = new DiagnosisKeysDateDirectoryImpl(diagnosisKeys,
34           cryptoProvider);
35       return decorateDateDirectory(dateDirectory);
36     });
37     super.prepare(indices);
38   }
39 
40   private Directory decorateDateDirectory(IndexDirectory<LocalDate> dateDirectory) {
41     return new DateAggregatingDecorator(new IndexingDecorator<>(dateDirectory), cryptoProvider);
42   }
43 }
44 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.diagnosiskeys.util.DateTime;
6 import app.coronawarn.server.services.distribution.structure.directory.Directory;
7 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
8 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
9 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
10 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
11 import java.time.LocalDate;
12 import java.time.LocalDateTime;
13 import java.time.format.DateTimeFormatter;
14 import java.util.Collection;
15 
16 public class DiagnosisKeysDateDirectoryImpl extends IndexDirectoryImpl<LocalDate> {
17 
18   private static final String DATE_DIRECTORY = "date";
19   private static final DateTimeFormatter ISO8601 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
20 
21   private Collection<DiagnosisKey> diagnosisKeys;
22   private CryptoProvider cryptoProvider;
23 
24   public DiagnosisKeysDateDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
25       CryptoProvider cryptoProvider) {
26     super(DATE_DIRECTORY, __ -> DateTime.getDates(diagnosisKeys), ISO8601::format);
27     this.diagnosisKeys = diagnosisKeys;
28     this.cryptoProvider = cryptoProvider;
29   }
30 
31   @Override
32   public void prepare(ImmutableStack<Object> indices) {
33     this.addDirectoryToAll(currentIndices -> {
34       LocalDate currentDate = (LocalDate) currentIndices.peek();
35       IndexDirectory<LocalDateTime> hourDirectory = new DiagnosisKeysHourDirectoryImpl(
36           diagnosisKeys, currentDate, cryptoProvider);
37       return decorateHourDirectory(hourDirectory);
38     });
39     super.prepare(indices);
40   }
41 
42   private Directory decorateHourDirectory(IndexDirectory<LocalDateTime> hourDirectory) {
43     return new IndexingDecorator<>(hourDirectory);
44   }
45 }
46 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.structure.directory.Directory;
6 import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
7 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
8 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
9 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
10 import java.util.Collection;
11 
12 /**
13  * A {@link Directory} containing the file and directory structure that mirrors the API defined in
14  * the OpenAPI definition {@code /services/distribution/api_v1.json}. Available countries (endpoint
15  * {@code /version/v1/diagnosis-keys/country}) are statically set to only {@code "DE"}. The dates
16  * and respective hours (endpoint {@code /version/v1/diagnosis-keys/country/DE/date}) will be
17  * created based on the actual {@link DiagnosisKey DiagnosisKeys} given to the {@link
18  * DiagnosisKeysDirectoryImpl#DiagnosisKeysDirectoryImpl constructor}.
19  */
20 public class DiagnosisKeysDirectoryImpl extends DirectoryImpl {
21 
22   private static final String DIAGNOSIS_KEYS_DIRECTORY = "diagnosis-keys";
23   private final Collection<DiagnosisKey> diagnosisKeys;
24   private final CryptoProvider cryptoProvider;
25 
26   public DiagnosisKeysDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
27       CryptoProvider cryptoProvider) {
28     super(DIAGNOSIS_KEYS_DIRECTORY);
29     this.diagnosisKeys = diagnosisKeys;
30     this.cryptoProvider = cryptoProvider;
31   }
32 
33   @Override
34   public void prepare(ImmutableStack<Object> indices) {
35     this.addDirectory(decorateCountryDirectory(
36         new DiagnosisKeysCountryDirectoryImpl(diagnosisKeys, cryptoProvider)));
37     super.prepare(indices);
38   }
39 
40   private Directory decorateCountryDirectory(IndexDirectory<String> countryDirectory) {
41     return new IndexingDecorator<>(countryDirectory);
42   }
43 }
44 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.diagnosiskeys.structure.file.HourFileImpl;
6 import app.coronawarn.server.services.distribution.diagnosiskeys.util.DateTime;
7 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
8 import app.coronawarn.server.services.distribution.structure.file.File;
9 import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
10 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
11 import java.time.LocalDate;
12 import java.time.LocalDateTime;
13 import java.util.Collection;
14 
15 public class DiagnosisKeysHourDirectoryImpl extends IndexDirectoryImpl<LocalDateTime> {
16 
17   private static final String HOUR_DIRECTORY = "hour";
18 
19   private Collection<DiagnosisKey> diagnosisKeys;
20   private LocalDate currentDate;
21   private CryptoProvider cryptoProvider;
22 
23   public DiagnosisKeysHourDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
24       LocalDate currentDate, CryptoProvider cryptoProvider) {
25     super(HOUR_DIRECTORY, indices -> {
26       return DateTime.getHours(((LocalDate) indices.peek()), diagnosisKeys);
27     }, LocalDateTime::getHour);
28     this.diagnosisKeys = diagnosisKeys;
29     this.currentDate = currentDate;
30     this.cryptoProvider = cryptoProvider;
31   }
32 
33   @Override
34   public void prepare(ImmutableStack<Object> indices) {
35     this.addFileToAll(currentIndices -> {
36       LocalDateTime currentHour = (LocalDateTime) currentIndices.peek();
37       // The LocalDateTime currentHour already contains both the date and the hour information, so
38       // we can throw away the LocalDate that's the second item on the stack from the "/date" IndexDirectory.
39       String region = (String) currentIndices.pop().pop().peek();
40       return decorateHourFile(new HourFileImpl(currentHour, region, diagnosisKeys));
41     });
42     super.prepare(indices);
43   }
44 
45   private File decorateHourFile(File hourFile) {
46     return new SigningDecorator(hourFile, cryptoProvider);
47   }
48 }
49 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory.decorator;
2 
3 import app.coronawarn.server.common.protocols.internal.FileBucket;
4 import app.coronawarn.server.common.protocols.internal.SignedPayload;
5 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
6 import app.coronawarn.server.services.distribution.structure.Writable;
7 import app.coronawarn.server.services.distribution.structure.directory.Directory;
8 import app.coronawarn.server.services.distribution.structure.directory.decorator.DirectoryDecorator;
9 import app.coronawarn.server.services.distribution.structure.file.File;
10 import app.coronawarn.server.services.distribution.structure.file.FileImpl;
11 import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
12 import app.coronawarn.server.services.distribution.structure.functional.CheckedFunction;
13 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
14 import java.util.ArrayList;
15 import java.util.Comparator;
16 import java.util.List;
17 import java.util.Set;
18 import java.util.stream.Collectors;
19 import java.util.stream.Stream;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 
23 /**
24  * A {@link DirectoryDecorator} that will create a {@link SignedPayload} containing a {@link
25  * FileBucket} for each date within its directory.
26  */
27 public class DateAggregatingDecorator extends DirectoryDecorator {
28 
29   private static final Logger logger = LoggerFactory.getLogger(DateAggregatingDecorator.class);
30 
31   private final CryptoProvider cryptoProvider;
32 
33   private static final String AGGREGATE_FILE_NAME = "index";
34 
35   public DateAggregatingDecorator(Directory directory, CryptoProvider cryptoProvider) {
36     super(directory);
37     this.cryptoProvider = cryptoProvider;
38   }
39 
40   @Override
41   public void prepare(ImmutableStack<Object> indices) {
42     super.prepare(indices);
43     logger.debug("Aggregating {}", this.getFileOnDisk().getPath());
44     Set<Directory> days = this.getDirectories();
45     if (days.size() == 0) {
46       return;
47     }
48 
49     List<Directory> sortedDays = new ArrayList<>(days);
50     sortedDays.sort(Comparator.comparing(Writable::getName));
51 
52     // Exclude the last day
53     sortedDays.subList(0, days.size() - 1).forEach(currentDirectory -> {
54       Stream.of(currentDirectory)
55           .map(this::getSubSubDirectoryFiles)
56           .map(this::parseFileBucketsFromFiles)
57           .map(this::reduceFileBuckets)
58           .map(this::makeNewFileBucket)
59           .map(FileBucket::toByteArray)
60           .map(bytes -> new FileImpl(AGGREGATE_FILE_NAME, bytes))
61           .map(file -> new SigningDecorator(file, cryptoProvider))
62           .peek(currentDirectory::addFile)
63           .forEach(aggregate -> aggregate.prepare(indices));
64     });
65   }
66 
67   private Set<File> getSubSubDirectoryFiles(Directory directory) {
68     // Get all files 2 directory levels down
69     return Stream.of(directory)
70         .map(Directory::getDirectories)
71         .flatMap(Set::stream)
72         .map(Directory::getDirectories)
73         .flatMap(Set::stream)
74         .map(Directory::getFiles)
75         .flatMap(Set::stream)
76         .collect(Collectors.toSet());
77   }
78 
79   private Set<FileBucket> parseFileBucketsFromFiles(Set<File> files) {
80     return files.stream()
81         .map(File::getBytes)
82         .map(CheckedFunction.uncheckedFunction(SignedPayload::parseFrom))
83         .map(SignedPayload::getPayload)
84         .map(CheckedFunction.uncheckedFunction(FileBucket::parseFrom))
85         .collect(Collectors.toSet());
86   }
87 
88   private Set<app.coronawarn.server.common.protocols.external.exposurenotification.File> reduceFileBuckets(
89       Set<FileBucket> fileBuckets) {
90     return fileBuckets.stream()
91         .map(FileBucket::getFilesList)
92         .flatMap(List::stream)
93         .collect(Collectors.toSet());
94   }
95 
96   private FileBucket makeNewFileBucket(
97       Set<app.coronawarn.server.common.protocols.external.exposurenotification.File> enFiles) {
98     return FileBucket.newBuilder()
99         .addAllFiles(enFiles)
100         .build();
101   }
102 }
103 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/file/HourFileImpl.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.structure.file;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import app.coronawarn.server.common.protocols.external.exposurenotification.File;
5 import app.coronawarn.server.common.protocols.external.exposurenotification.Key;
6 import app.coronawarn.server.common.protocols.internal.FileBucket;
7 import app.coronawarn.server.services.distribution.diagnosiskeys.util.Batch;
8 import app.coronawarn.server.services.distribution.diagnosiskeys.util.DateTime;
9 import app.coronawarn.server.services.distribution.structure.file.FileImpl;
10 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
11 import com.google.protobuf.ByteString;
12 import java.time.Instant;
13 import java.time.LocalDateTime;
14 import java.time.ZoneOffset;
15 import java.util.Collection;
16 import java.util.Set;
17 import java.util.stream.Collectors;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20 
21 public class HourFileImpl extends FileImpl {
22 
23   private static final Logger logger = LoggerFactory.getLogger(HourFileImpl.class);
24 
25   private static final String INDEX_FILE_NAME = "index";
26 
27   private final LocalDateTime currentHour;
28   private final String region;
29   private final Collection<DiagnosisKey> diagnosisKeys;
30 
31   public HourFileImpl(LocalDateTime currentHour, String region,
32       Collection<DiagnosisKey> diagnosisKeys) {
33     super(INDEX_FILE_NAME, new byte[0]);
34     this.currentHour = currentHour;
35     this.region = region;
36     this.diagnosisKeys = diagnosisKeys;
37   }
38 
39   @Override
40   public void prepare(ImmutableStack<Object> indices) {
41     this.setBytes(createHourBytes());
42     super.prepare(indices);
43   }
44 
45   private byte[] createHourBytes() {
46     logger.debug("Creating hour file for {}", currentHour);
47     return FileBucket.newBuilder()
48         .addAllFiles(generateFiles(diagnosisKeys, currentHour, region))
49         .build()
50         .toByteArray();
51   }
52 
53   private static Set<File> generateFiles(Collection<DiagnosisKey> diagnosisKeys,
54       LocalDateTime currentHour, String region) {
55     Instant startTimestamp = Instant.from(currentHour.atOffset(ZoneOffset.UTC));
56     Instant endTimestamp = Instant.from(currentHour.atOffset(ZoneOffset.UTC).plusHours(1));
57     Set<Key> keys = createKeys(diagnosisKeys, currentHour);
58     return Batch.aggregateKeys(keys, startTimestamp, endTimestamp, region);
59   }
60 
61   private static Set<Key> createKeys(Collection<DiagnosisKey> diagnosisKeys,
62       LocalDateTime currentHour) {
63     return diagnosisKeys.stream()
64         .filter(diagnosisKey -> DateTime
65             .getLocalDateTimeFromHoursSinceEpoch(diagnosisKey.getSubmissionTimestamp())
66             .equals(currentHour))
67         .map(HourFileImpl::createKey)
68         .collect(Collectors.toSet());
69   }
70 
71   private static Key createKey(DiagnosisKey key) {
72     return Key.newBuilder()
73         .setKeyData(ByteString.copyFrom(key.getKeyData()))
74         .setRollingStartNumber(Math.toIntExact(key.getRollingStartNumber()))
75         .setRollingPeriod(Math.toIntExact(key.getRollingPeriod()))
76         .setTransmissionRiskLevel(key.getTransmissionRiskLevel())
77         .build();
78   }
79 }
80 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/file/HourFileImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Batch.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.util;
2 
3 import app.coronawarn.server.common.protocols.external.exposurenotification.File;
4 import app.coronawarn.server.common.protocols.external.exposurenotification.Header;
5 import app.coronawarn.server.common.protocols.external.exposurenotification.Key;
6 import java.time.Instant;
7 import java.util.ArrayList;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12 import java.util.stream.IntStream;
13 
14 /**
15  * Functionality to split collections of {@link Key keys} into similar sized collections of {@link
16  * java.io.File files} containing the respecitve key data.
17  */
18 public class Batch {
19 
20   private static final int KILO = 1000;
21   private static final int FILE_SIZE_LIMIT_KB = 500;
22   private static final int FILE_SIZE_LIMIT_BYTES = FILE_SIZE_LIMIT_KB * KILO;
23 
24   /**
25    * Aggregates a set of {@link Key Keys} into a set of {@link File Files} of roughly equal size.
26    */
27   public static Set<File> aggregateKeys(Set<Key> keys, Instant startTimestamp,
28       Instant endTimeStamp, String region) {
29     // Because protocol buffers optimize each serialization based on the content, we can not exactly
30     // calculate the file size that any given serialization will produce ahead of time. So, in order
31     // to know into how many batches we will need to split the keys, we simply "attempt" to
32     // serialize all keys into a single file and measure its size. If we find that the resulting
33     // file goes above the file size limit, we simply partition the keys into N + 1 partitions of
34     // approximately equal size, where N = (first file size / file size limit). We add one to N
35     // for good measure, to avoid edge cases, rounding errors and varying file sizes after
36     // serialization.
37     File singleFile = aggregateKeysIntoBatches(keys, 1, startTimestamp, endTimeStamp, region)
38         .stream()
39         .findFirst()
40         .orElseThrow();
41     int singleFileSize = singleFile.getSerializedSize();
42     if (singleFileSize > FILE_SIZE_LIMIT_BYTES) {
43       int numBatches = Maths.ceilDiv(singleFileSize, FILE_SIZE_LIMIT_BYTES) + 1;
44       return aggregateKeysIntoBatches(keys, numBatches, startTimestamp, endTimeStamp, region);
45     } else {
46       return Set.of(singleFile);
47     }
48   }
49 
50   /**
51    * Aggregates a set of {@link Key Keys} into a set of equally sized {@link File Files}.
52    */
53   private static Set<File> aggregateKeysIntoBatches(Set<Key> keys, int numBatches,
54       Instant startTimestamp, Instant endTimeStamp, String region) {
55     List<Set<Key>> partitions = partitionSet(keys, numBatches);
56     return IntStream.range(0, partitions.size())
57         .mapToObj(index -> {
58           Header header = Header.newBuilder()
59               .setStartTimestamp(startTimestamp.toEpochMilli())
60               .setEndTimestamp(endTimeStamp.toEpochMilli())
61               .setRegion(region)
62               .setBatchNum(index + 1)
63               .setBatchSize(numBatches)
64               .build();
65           Set<Key> partition = partitions.get(index);
66           return File
67               .newBuilder()
68               .setHeader(header)
69               .addAllKeys(partition)
70               .build();
71         })
72         .collect(Collectors.toSet());
73   }
74 
75   /**
76    * Partitions a set into {@code numPartitions} equally sized sets.
77    *
78    * @param set           The set to partition
79    * @param numPartitions The number of partitions
80    * @return A list of sets of equal size
81    */
82   private static <T> List<Set<T>> partitionSet(Set<T> set, int numPartitions) {
83     int partitionSize = Maths.ceilDiv(set.size(), numPartitions);
84     List<T> list = new ArrayList<>(set);
85     return IntStream.range(0, numPartitions)
86         .mapToObj(currentPartition -> new HashSet<>(list.subList(partitionSize * currentPartition,
87             Math.min(currentPartition * partitionSize + partitionSize, list.size()))))
88         .collect(Collectors.toList());
89   }
90 }
91 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Batch.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTime.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.util;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import java.time.LocalDate;
5 import java.time.LocalDateTime;
6 import java.time.ZoneOffset;
7 import java.util.Collection;
8 import java.util.Set;
9 import java.util.concurrent.TimeUnit;
10 import java.util.stream.Collectors;
11 
12 /**
13  * Methods for conversions of time/date data.
14  */
15 public class DateTime {
16 
17   /**
18    * Returns a set of all {@link LocalDate dates} that are associated with the submission timestamps
19    * of the specified {@link DiagnosisKey diagnosis keys}.
20    */
21   public static Set<LocalDate> getDates(Collection<DiagnosisKey> diagnosisKeys) {
22     return diagnosisKeys.stream()
23         .map(DiagnosisKey::getSubmissionTimestamp)
24         .map(timestamp -> LocalDate.ofEpochDay(timestamp / 24))
25         .collect(Collectors.toSet());
26   }
27 
28 
29   /**
30    * Returns a set of all {@link LocalDateTime hours} that are associated with the submission
31    * timestamps of the specified {@link DiagnosisKey diagnosis keys} and the specified {@link
32    * LocalDate date}.
33    */
34   public static Set<LocalDateTime> getHours(LocalDate currentDate,
35       Collection<DiagnosisKey> diagnosisKeys) {
36     return diagnosisKeys.stream()
37         .map(DiagnosisKey::getSubmissionTimestamp)
38         .map(DateTime::getLocalDateTimeFromHoursSinceEpoch)
39         .filter(currentDateTime -> currentDateTime.toLocalDate().equals(currentDate))
40         .collect(Collectors.toSet());
41   }
42 
43   /**
44    * Creates a {@link LocalDateTime} based on the specified epoch timestamp.
45    */
46   public static LocalDateTime getLocalDateTimeFromHoursSinceEpoch(long timestamp) {
47     return LocalDateTime.ofEpochSecond(TimeUnit.HOURS.toSeconds(timestamp), 0, ZoneOffset.UTC);
48   }
49 }
50 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTime.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Maths.java]
1 package app.coronawarn.server.services.distribution.diagnosiskeys.util;
2 
3 public class Maths {
4 
5   public static int ceilDiv(int numerator, int denominator) {
6     return (numerator + denominator - 1) / denominator;
7   }
8 }
9 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Maths.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProvider.java]
1 package app.coronawarn.server.services.distribution.exposureconfig;
2 
3 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
4 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters.Builder;
5 import app.coronawarn.server.services.distribution.exposureconfig.parsing.YamlConstructorForProtoBuf;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import org.springframework.core.io.ClassPathResource;
9 import org.springframework.core.io.Resource;
10 import org.yaml.snakeyaml.Yaml;
11 import org.yaml.snakeyaml.error.YAMLException;
12 import org.yaml.snakeyaml.introspector.BeanAccess;
13 
14 /**
15  * Provides the Exposure Configuration based on a file in the fileystem.<br> The existing file must
16  * be a valid YAML file, and must match the specification of the proto file
17  * risk_score_parameters.proto.
18  */
19 public class ExposureConfigurationProvider {
20 
21   private ExposureConfigurationProvider() {
22   }
23 
24   /**
25    * The location of the exposure configuration master file.
26    */
27   public static final String MASTER_FILE = "exposure-config/master.yaml";
28 
29   /**
30    * Fetches the master configuration as a RiskScoreParameters instance.
31    *
32    * @return the exposure configuration as RiskScoreParameters
33    * @throws UnableToLoadFileException when the file/transformation did not succeed
34    */
35   public static RiskScoreParameters readMasterFile() throws UnableToLoadFileException {
36     return readFile(MASTER_FILE);
37   }
38 
39   /**
40    * Fetches an exposure configuration file based on the given path. The path must be available in
41    * the classloader.
42    *
43    * @param path the path, e.g. folder/my-exposure-configuration.yaml
44    * @return the RiskScoreParameters
45    * @throws UnableToLoadFileException when the file/transformation did not succeed
46    */
47   public static RiskScoreParameters readFile(String path) throws UnableToLoadFileException {
48     Yaml yaml = new Yaml(new YamlConstructorForProtoBuf());
49     yaml.setBeanAccess(BeanAccess.FIELD); /* no setters on RiskScoreParameters available */
50 
51     Resource riskScoreParametersResource = new ClassPathResource(path);
52     try (InputStream inputStream = riskScoreParametersResource.getInputStream()) {
53       Builder loaded = yaml.loadAs(inputStream, RiskScoreParameters.newBuilder().getClass());
54       if (loaded == null) {
55         throw new UnableToLoadFileException(path);
56       }
57 
58       return loaded.build();
59     } catch (YAMLException e) {
60       throw new UnableToLoadFileException("Parsing failed", e);
61     } catch (IOException e) {
62       throw new UnableToLoadFileException("Failed to load file " + path, e);
63     }
64   }
65 }
66 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProvider.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/UnableToLoadFileException.java]
1 package app.coronawarn.server.services.distribution.exposureconfig;
2 
3 /**
4  * The file could not be loaded/parsed correctly.
5  */
6 public class UnableToLoadFileException extends Exception {
7 
8   public UnableToLoadFileException(String message) {
9     super(message);
10   }
11 
12   public UnableToLoadFileException(String message, Throwable cause) {
13     super(message, cause);
14   }
15 }
16 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/UnableToLoadFileException.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/parsing/YamlConstructorForProtoBuf.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.parsing;
2 
3 import java.util.Arrays;
4 import org.yaml.snakeyaml.constructor.Constructor;
5 import org.yaml.snakeyaml.introspector.BeanAccess;
6 import org.yaml.snakeyaml.introspector.Property;
7 import org.yaml.snakeyaml.introspector.PropertyUtils;
8 
9 /**
10  * This Constructor implementation grants SnakeYaml compliance with the generated proto Java
11  * classes.
12  * SnakeYaml expects the Java properties to have the same name as the yaml properties. But the
13  * generated Java classes' properties have an added suffix of '_'.
14  * In addition, this implementation also allows snake case in the YAML (for better readability), as
15  * the Java properties are transformed to camel case.
16  */
17 public class YamlConstructorForProtoBuf extends Constructor {
18 
19   public YamlConstructorForProtoBuf() {
20     setPropertyUtils(new ProtoBufPropertyUtils());
21   }
22 
23   private class ProtoBufPropertyUtils extends PropertyUtils {
24 
25     public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess) {
26       return super.getProperty(type, transformToProtoNaming(name), bAccess);
27     }
28 
29     private String transformToProtoNaming(String yamlPropertyName) {
30       return snakeToCamelCase(yamlPropertyName) + "_";
31     }
32 
33     private String snakeToCamelCase(String snakeCase) {
34       String camelCase = Arrays.stream(snakeCase.split("_"))
35           .map(word -> Character.toUpperCase(word.charAt(0)) + word.substring(1))
36           .reduce("", String::concat);
37 
38       return Character.toLowerCase(camelCase.charAt(0)) + camelCase.substring(1);
39     }
40   }
41 
42 }
43 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/parsing/YamlConstructorForProtoBuf.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.structure;
2 
3 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
6 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
7 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
8 import app.coronawarn.server.services.distribution.structure.file.FileImpl;
9 import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
10 import java.util.Set;
11 
12 /**
13  * Creates the directory structure {@code /parameters/country/:country} and writes a file called
14  * {@code index} containing {@link RiskScoreParameters} wrapped in a {@link
15  * app.coronawarn.server.common.protocols.internal.SignedPayload}.
16  */
17 public class ExposureConfigurationDirectoryImpl extends DirectoryImpl {
18 
19   private static final String PARAMETERS_DIRECTORY = "parameters";
20   private static final String COUNTRY_DIRECTORY = "country";
21   private static final String INDEX_FILE_NAME = "index";
22 
23   /**
24    * Constructor.
25    *
26    * @param region         The region that the {@link RiskScoreParameters} apply to.
27    * @param exposureConfig The {@link RiskScoreParameters} to sign and write.
28    * @param cryptoProvider The {@link CryptoProvider} whose artifacts to use for creating the {@link
29    *                       app.coronawarn.server.common.protocols.internal.SignedPayload}.
30    */
31   public ExposureConfigurationDirectoryImpl(
32       String region, RiskScoreParameters exposureConfig, CryptoProvider cryptoProvider) {
33     super(PARAMETERS_DIRECTORY);
34 
35     IndexDirectoryImpl<String> country =
36         new IndexDirectoryImpl<>(COUNTRY_DIRECTORY, __ -> Set.of(region), Object::toString);
37 
38     country.addFileToAll(__ ->
39         new SigningDecorator(new FileImpl(INDEX_FILE_NAME, exposureConfig.toByteArray()),
40             cryptoProvider));
41 
42     this.addDirectory(new IndexingDecorator<>(country));
43   }
44 }
45 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.structure.directory;
2 
3 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
6 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
7 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
8 import app.coronawarn.server.services.distribution.structure.file.FileImpl;
9 import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
10 import java.util.Set;
11 
12 /**
13  * Creates the directory structure {@code /parameters/country/:country} and writes a file called
14  * {@code index} containing {@link RiskScoreParameters} wrapped in a {@link
15  * app.coronawarn.server.common.protocols.internal.SignedPayload}.
16  */
17 public class ExposureConfigurationDirectoryImpl extends DirectoryImpl {
18 
19   private static final String PARAMETERS_DIRECTORY = "parameters";
20   private static final String COUNTRY_DIRECTORY = "country";
21   private static final String COUNTRY = "DE";
22   private static final String INDEX_FILE_NAME = "index";
23 
24   /**
25    * Constructor.
26    *
27    * @param exposureConfig The {@link RiskScoreParameters} to sign and write.
28    * @param cryptoProvider The {@link CryptoProvider} whose artifacts to use for creating the {@link
29    *                       app.coronawarn.server.common.protocols.internal.SignedPayload}.
30    */
31   public ExposureConfigurationDirectoryImpl(RiskScoreParameters exposureConfig,
32       CryptoProvider cryptoProvider) {
33     super(PARAMETERS_DIRECTORY);
34 
35     IndexDirectoryImpl<String> country =
36         new IndexDirectoryImpl<>(COUNTRY_DIRECTORY, __ -> Set.of(COUNTRY), Object::toString);
37 
38     country.addFileToAll(__ ->
39         new SigningDecorator(new FileImpl(INDEX_FILE_NAME, exposureConfig.toByteArray()),
40             cryptoProvider));
41 
42     this.addDirectory(new IndexingDecorator<>(country));
43   }
44 }
45 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidator.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 import app.coronawarn.server.common.protocols.internal.RiskLevel;
4 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
5 import app.coronawarn.server.services.distribution.exposureconfig.validation.WeightValidationError.ErrorType;
6 import java.beans.BeanInfo;
7 import java.beans.IntrospectionException;
8 import java.beans.Introspector;
9 import java.beans.PropertyDescriptor;
10 import java.lang.reflect.InvocationTargetException;
11 import java.math.BigDecimal;
12 import java.util.Arrays;
13 
14 /**
15  * The Exposure Configuration Validator checks the values of a given RiskScoreParameters instance.
16  * Validation is performed according to the Apple/Google spec.<br>
17  * <br>
18  * Weights must be in the range of 0.001 to 100.<br> Scores must be in the range of 1 to 8.<br>
19  */
20 public class ExposureConfigurationValidator {
21 
22   private RiskScoreParameters config;
23 
24   private ValidationResult errors;
25 
26   public ExposureConfigurationValidator(RiskScoreParameters config) {
27     this.config = config;
28   }
29 
30   /**
31    * Triggers the validation of the configuration.
32    *
33    * @return the ValidationResult instance, containing information about possible errors.
34    * @throws ValidationFailedException in case the validation could not be performed
35    */
36   public ValidationResult validate() {
37     this.errors = new ValidationResult();
38 
39     validateWeights();
40 
41     try {
42       validateParameterRiskLevels("duration", config.getDuration());
43       validateParameterRiskLevels("transmission", config.getTransmission());
44       validateParameterRiskLevels("daysSinceLastExposure", config.getDaysSinceLastExposure());
45       validateParameterRiskLevels("attenuation", config.getAttenuation());
46     } catch (IntrospectionException e) {
47       throw new ValidationFailedException("Unable to check risk levels", e);
48     }
49 
50     return errors;
51   }
52 
53   private void validateParameterRiskLevels(String name, Object object)
54       throws IntrospectionException {
55     BeanInfo bean = Introspector.getBeanInfo(object.getClass());
56 
57     Arrays.stream(bean.getPropertyDescriptors())
58         .filter(propertyDescriptor -> propertyDescriptor.getPropertyType() == RiskLevel.class)
59         .forEach(propertyDescriptor -> validateScore(propertyDescriptor, object, name));
60   }
61 
62   private void validateScore(
63       PropertyDescriptor propertyDescriptor, Object object, String parameter) {
64     try {
65       RiskLevel level = (RiskLevel) propertyDescriptor.getReadMethod().invoke(object);
66 
67       if (level == RiskLevel.UNRECOGNIZED || level == RiskLevel.RISK_LEVEL_UNSPECIFIED) {
68         this.errors.add(new RiskLevelValidationError(parameter, propertyDescriptor.getName()));
69       }
70     } catch (IllegalAccessException | InvocationTargetException e) {
71       throw new ValidationFailedException(
72           "Unable to read property " + propertyDescriptor.getName(), e);
73     }
74   }
75 
76   private void validateWeights() {
77     validateWeight(config.getTransmissionWeight(), "transmission");
78     validateWeight(config.getDurationWeight(), "duration");
79     validateWeight(config.getAttenuationWeight(), "attenuation");
80   }
81 
82   private void validateWeight(double weight, String name) {
83     if (isOutOfRange(ParameterSpec.WEIGHT_MIN, ParameterSpec.WEIGHT_MAX, weight)) {
84       this.errors.add(new WeightValidationError(name, weight, ErrorType.OUT_OF_RANGE));
85     }
86 
87     if (!respectsMaximumDecimalPlaces(weight)) {
88       this.errors.add(new WeightValidationError(name, weight, ErrorType.TOO_MANY_DECIMAL_PLACES));
89     }
90   }
91 
92   private boolean respectsMaximumDecimalPlaces(double weight) {
93     BigDecimal bd = new BigDecimal(String.valueOf(weight));
94 
95     return bd.scale() <= ParameterSpec.WEIGHT_MAX_DECIMALS;
96   }
97 
98 
99   private boolean isOutOfRange(double min, double max, double x) {
100     return x < min || x > max;
101   }
102 }
103 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ParameterSpec.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 /**
4  * Definition of the spec according to Apple/Google:
5  * https://developer.apple.com/documentation/exposurenotification/enexposureconfiguration
6  */
7 public class ParameterSpec {
8 
9   private ParameterSpec() {
10   }
11 
12   /** The minimum weight value for mobile API. */
13   public static final double WEIGHT_MIN = 0.001;
14 
15   /** The maximum weight value for mobile API. */
16   public static final int WEIGHT_MAX = 100;
17 
18   /** Maximmum number of allowed decimals. */
19   public static final int WEIGHT_MAX_DECIMALS = 3;
20 
21 }
22 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ParameterSpec.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/RiskLevelValidationError.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 import java.util.Objects;
4 
5 /**
6  * Defines a validation errors for risk levels, used by the parameter scores.
7  */
8 public class RiskLevelValidationError implements ValidationError {
9 
10   private final String parameter;
11 
12   private final String riskLevel;
13 
14   public RiskLevelValidationError(String parameter, String riskLevel) {
15     this.parameter = parameter;
16     this.riskLevel = riskLevel;
17   }
18 
19   public String getParameter() {
20     return parameter;
21   }
22 
23   public String getRiskLevel() {
24     return riskLevel;
25   }
26 
27   @Override
28   public boolean equals(Object o) {
29     if (this == o) {
30       return true;
31     }
32     if (o == null || getClass() != o.getClass()) {
33       return false;
34     }
35     RiskLevelValidationError that = (RiskLevelValidationError) o;
36     return Objects.equals(getParameter(), that.getParameter())
37         && Objects.equals(getRiskLevel(), that.getRiskLevel());
38   }
39 
40   @Override
41   public int hashCode() {
42     return Objects.hash(getParameter(), getRiskLevel());
43   }
44 
45   @Override
46   public String toString() {
47     return "RiskLevelValidationError{" +
48         "parameter='" + parameter + '\'' +
49         ", riskLevel='" + riskLevel + '\'' +
50         '}';
51   }
52 }
53 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/RiskLevelValidationError.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationError.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 /**
4  * A validation error, found during the process of validating the Exposure Configuration. Can either
5  * be score or weight related.
6  */
7 public interface ValidationError {
8 
9 }
10 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationError.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationFailedException.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 /**
4  * The validation could not be executed. Find more information about the root cause in the cause
5  * element, and in the message property.<br>
6  */
7 public class ValidationFailedException extends RuntimeException {
8 
9   public ValidationFailedException(String message, Throwable cause) {
10     super(message, cause);
11   }
12 }
13 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationFailedException.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationResult.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 import java.util.HashSet;
4 
5 /**
6  * The result of a validation run for Exposure Configurations. Find details about possible
7  * errors in this collection.
8  */
9 public class ValidationResult extends HashSet<ValidationError> {
10 
11   /**
12    * Checks whether this validation result instance has at least one error.
13    *
14    * @return true if yes, false otherwise
15    */
16   public boolean hasErrors() {
17     return !this.isEmpty();
18   }
19 
20   /**
21    * Checks whether this validation result instance has no errors.
22    *
23    * @return true if yes, false otherwise
24    */
25   public boolean isSuccessful() {
26     return !hasErrors();
27   }
28 }
29 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationResult.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/WeightValidationError.java]
1 package app.coronawarn.server.services.distribution.exposureconfig.validation;
2 
3 import java.util.Objects;
4 
5 public class WeightValidationError implements ValidationError {
6 
7   private final ErrorType errorType;
8 
9   private final String parameter;
10 
11   private final double givenValue;
12 
13   public WeightValidationError(String parameter, double givenValue, ErrorType errorType) {
14     this.parameter = parameter;
15     this.givenValue = givenValue;
16     this.errorType = errorType;
17   }
18 
19   public String getParameter() {
20     return parameter;
21   }
22 
23   public double getGivenValue() {
24     return givenValue;
25   }
26 
27   public ErrorType getErrorType() {
28     return errorType;
29   }
30 
31   @Override
32   public boolean equals(Object o) {
33     if (this == o) {
34       return true;
35     }
36     if (o == null || getClass() != o.getClass()) {
37       return false;
38     }
39     WeightValidationError that = (WeightValidationError) o;
40     return Double.compare(that.getGivenValue(), getGivenValue()) == 0
41         && getErrorType() == that.getErrorType()
42         && Objects.equals(getParameter(), that.getParameter());
43   }
44 
45   @Override
46   public int hashCode() {
47     return Objects.hash(getErrorType(), getParameter(), getGivenValue());
48   }
49 
50   @Override
51   public String toString() {
52     return "WeightValidationError{" +
53         "errorType=" + errorType +
54         ", parameter='" + parameter + '\'' +
55         ", givenValue=" + givenValue +
56         '}';
57   }
58 
59   public enum ErrorType {
60     OUT_OF_RANGE,
61     TOO_MANY_DECIMAL_PLACES
62   }
63 }
64 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/WeightValidationError.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/io/IO.java]
1 package app.coronawarn.server.services.distribution.io;
2 
3 import java.io.File;
4 import java.io.FileOutputStream;
5 import java.io.IOException;
6 import java.nio.file.Files;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 
10 /**
11  * A class containing helper functions for general purpose file IO.
12  */
13 public class IO {
14 
15   private static final Logger logger = LoggerFactory.getLogger(IO.class);
16 
17   /**
18    * Creates a new file on disk.
19    *
20    * @param root The parent file.
21    * @param name The name of the new file.
22    */
23   public static void makeFile(File root, String name) {
24     File directory = new File(root, name);
25     try {
26       directory.createNewFile();
27     } catch (IOException e) {
28       logger.error("Failed to create file: {}", name, e);
29       throw new RuntimeException(e);
30     }
31   }
32 
33   /**
34    * Reads the contents of a file as {@code byte[]}.
35    *
36    * @param file The file to read.
37    * @return The contents of the file as {@code byte[]}.
38    */
39   public static byte[] getBytesFromFile(File file) {
40     try {
41       return Files.readAllBytes(file.toPath());
42     } catch (IOException e) {
43       logger.error("Read operation failed.", e);
44       throw new RuntimeException(e);
45     }
46   }
47 
48   /**
49    * Writes bytes into a file.
50    *
51    * @param bytes      The content to write
52    * @param outputFile The file to write the content into.
53    */
54   public static void writeBytesToFile(byte[] bytes, File outputFile) {
55     try (FileOutputStream outputFileStream = new FileOutputStream(outputFile)) {
56       outputFileStream.write(bytes);
57     } catch (IOException e) {
58       logger.error("Write operation failed.", e);
59       throw new RuntimeException(e);
60     }
61   }
62 }
63 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/io/IO.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java]
1 package app.coronawarn.server.services.distribution.objectstore;
2 
3 import java.io.File;
4 import java.net.URI;
5 import java.net.URISyntaxException;
6 import java.util.Collection;
7 import java.util.stream.Collectors;
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10 import org.springframework.beans.factory.annotation.Autowired;
11 import org.springframework.beans.factory.annotation.Value;
12 import org.springframework.stereotype.Component;
13 import software.amazon.awssdk.core.sync.RequestBody;
14 import software.amazon.awssdk.regions.Region;
15 import software.amazon.awssdk.services.s3.S3Client;
16 import software.amazon.awssdk.services.s3.model.Delete;
17 import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest;
18 import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
19 import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
20 import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
21 import software.amazon.awssdk.services.s3.model.PutObjectRequest;
22 
23 /**
24  * <p>Grants access to the object store, enabling basic functionality for working with files.</p>
25  * <p>Use S3Publisher for more convenient access.</p>
26  * <br>
27  * Make sure the following ENV vars are available.
28  * <ul>
29  *   <li>cwa.objectstore.endpoint</li>
30  *   <li>cwa.objectstore.bucket</li>
31  *   <li>AWS_ACCESS_KEY_ID</li>
32  *   <li>AWS_SECRET_ACCESS_KEY</li>
33  * </ul>
34  *
35  */
36 @Component
37 public class ObjectStoreAccess {
38 
39   private Logger logger = LoggerFactory.getLogger(this.getClass());
40 
41   private String bucket;
42 
43   private S3Client client;
44 
45   @Autowired
46   public ObjectStoreAccess(@Value("${cwa.objectstore.endpoint:notset}") String endpoint,
47       @Value("${cwa.objectstore.bucket:notset}") String bucket) throws URISyntaxException {
48     this.bucket = bucket;
49 
50     if ("notset".equals(endpoint) || "notset".equals(bucket)) {
51       logger.error("S3 Connection parameters missing - unable to serve S3 integration.");
52       return;
53     }
54 
55     this.client = S3Client.builder()
56         .endpointOverride(new URI(endpoint))
57         .region(Region.EU_CENTRAL_1) /* required by SDK, but ignored on S3 side */
58         .build();
59   }
60 
61   /**
62    * Stores an object in the object store.
63    *
64    * @param key the key to use, e.g. my/folder/struc/file.ext
65    * @param file the file to upload
66    */
67   public void putObject(String key, File file) {
68     RequestBody bodyFile = RequestBody.fromFile(file);
69 
70     this.client
71         .putObject(PutObjectRequest.builder().bucket(this.bucket).key(key).build(), bodyFile);
72   }
73 
74   /**
75    * Deletes objects in the object store, based on the given prefix (folder structure).
76    *
77    * @param prefix the prefix, e.g. my/folder/
78    */
79   public void deleteObjectsWithPrefix(String prefix) {
80     ListObjectsV2Response files = getObjectsWithPrefix(prefix);
81     Collection<ObjectIdentifier> identifiers = files
82         .contents()
83         .stream()
84         .map(s3object -> ObjectIdentifier.builder().key(s3object.key()).build())
85         .collect(Collectors.toList());
86 
87     this.client.deleteObjects(DeleteObjectsRequest.builder().bucket(this.bucket).delete(
88         Delete.builder().objects(identifiers).build()).build());
89   }
90 
91   /**
92    * Fetches the list of objects in the store with the given prefix.
93    *
94    * @param prefix the prefix, e.g. my/folder/
95    * @return the list of objects
96    */
97   public ListObjectsV2Response getObjectsWithPrefix(String prefix) {
98     return client
99         .listObjectsV2(ListObjectsV2Request.builder().prefix(prefix).bucket(this.bucket).build());
100   }
101 
102 }
103 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3Publisher.java]
1 package app.coronawarn.server.services.distribution.objectstore;
2 
3 import java.io.IOException;
4 import java.nio.file.Files;
5 import java.nio.file.Path;
6 import java.util.stream.Stream;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.stereotype.Component;
11 
12 /**
13  * This publisher is the interface to the S3, translating a local file structure to the target S3
14  * objects.
15  */
16 @Component
17 public class S3Publisher {
18 
19   private Logger logger = LoggerFactory.getLogger(this.getClass());
20 
21   /**
22    * prefix path on S3, enforced for all methods on this class.
23    */
24   private String prefixPath = "cwa/";
25 
26   private ObjectStoreAccess objectStoreAccess;
27 
28   @Autowired
29   public S3Publisher(ObjectStoreAccess objectStoreAccess) {
30     this.objectStoreAccess = objectStoreAccess;
31   }
32 
33   /**
34    * Publishes a local folder to S3. All files in the target folder will be copied over to S3,
35    * keeping the file structure. This operation is running also through all subfolders
36    * (recursively).
37    *
38    * @param path the folder on the local disk
39    * @throws IOException in case there was a problem reading the folder/contents
40    */
41   public void publishFolder(Path path) throws IOException {
42     if (!path.toFile().isDirectory()) {
43       throw new UnsupportedOperationException("Supplied path is not a folder: " + path);
44     }
45 
46     try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {
47       stream.filter(Files::isRegularFile).forEach(file -> publishFile(file, path));
48     }
49   }
50 
51   /**
52    * Deletes a folder on S3.
53    *
54    * @param path the folder to delete
55    */
56   public void deleteFolder(String path) {
57     objectStoreAccess.deleteObjectsWithPrefix(prefixPath + path);
58   }
59 
60   /**
61    * Publishes the given file.
62    *
63    * @param file the file to publish
64    * @param root the root, needed to compute the relative path for the S3 location
65    */
66   public void publishFile(Path file, Path root) {
67     if (!file.toFile().isFile()) {
68       throw new UnsupportedOperationException("Supplied path is not a file: " + file);
69     }
70     String publishingPath = createS3Key(file, root);
71 
72     logger.info("Publishing " + publishingPath);
73     this.objectStoreAccess.putObject(publishingPath, file.toFile());
74   }
75 
76   /**
77    * Checks whether the given file exists.
78    * <br>
79    * Both parameters are local on disk, and the S3 path is derived relatively by the root & file.
80    * E.g.:<br> file: /folder1/folder2/file<br> root: /folder1/<br>
81    * <br>
82    * The result S3 location will be /folder2/file, which will be checked for existence.
83    *
84    * @param file the file to check
85    * @param root the root, needed to compute the relative path for the S3 location
86    * @return true, if it exists, false otherwise
87    */
88   public boolean isFileExisting(Path file, Path root) {
89     return this.objectStoreAccess.getObjectsWithPrefix(createS3Key(file, root)).hasContents();
90   }
91 
92   /**
93    * Deletes the given file on S3.
94    * <br>
95    * Both parameters are local on disk, and the S3 path is derived relatively by the root & file
96    * E.g.:<br> file: /folder1/folder2/file<br> root: /folder1/<br>
97    * <br>
98    * The result S3 location will be /folder2/file, which will be deleted.
99    *
100    * @param file the file to delete
101    * @param root the root, needed to compute the relative path for the S3 location
102    */
103   public void deleteFile(Path file, Path root) {
104     this.objectStoreAccess.deleteObjectsWithPrefix(createS3Key(file, root));
105   }
106 
107   private String createS3Key(Path file, Path rootFolder) {
108     Path relativePath = rootFolder.relativize(file);
109     return s3path(relativePath.toString());
110   }
111 
112   private String s3path(String path) {
113     return prefixPath + path;
114   }
115 
116   /**
117    * Changes the prefix path for all S3Publisher operations. The default root.
118    *
119    * @param prefixPath the new prefix path
120    */
121   public void setPrefixPath(String prefixPath) {
122     this.prefixPath = prefixPath;
123   }
124 }
125 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3Publisher.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/DiagnosisKeyDistributionRunner.java]
1 package app.coronawarn.server.services.distribution.runner;
2 
3 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
4 import app.coronawarn.server.common.persistence.service.DiagnosisKeyService;
5 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
6 import app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory.DiagnosisKeysDirectoryImpl;
7 import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
8 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
9 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
10 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
11 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
12 import java.io.File;
13 import java.io.IOException;
14 import java.util.Collection;
15 import java.util.Set;
16 import org.apache.commons.io.FileUtils;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.beans.factory.annotation.Value;
21 import org.springframework.boot.ApplicationArguments;
22 import org.springframework.boot.ApplicationRunner;
23 import org.springframework.core.annotation.Order;
24 import org.springframework.stereotype.Component;
25 
26 @Component
27 @Order(3)
28 /**
29  * This runner retrieves stored diagnosis keys, the generates and persists the respective diagnosis
30  * key bundles.
31  */
32 public class DiagnosisKeyDistributionRunner implements ApplicationRunner {
33 
34   private static final Logger logger = LoggerFactory
35       .getLogger(DiagnosisKeyDistributionRunner.class);
36 
37   private static final String VERSION_DIRECTORY = "version";
38 
39   @Value("${app.coronawarn.server.services.distribution.version}")
40   private String version;
41 
42   @Value("${app.coronawarn.server.services.distribution.paths.output}")
43   private String outputPath;
44 
45   @Autowired
46   private CryptoProvider cryptoProvider;
47 
48   @Autowired
49   private DiagnosisKeyService diagnosisKeyService;
50 
51 
52   @Override
53   public void run(ApplicationArguments args) throws IOException {
54     Collection<DiagnosisKey> diagnosisKeys = diagnosisKeyService.getDiagnosisKeys();
55 
56     DiagnosisKeysDirectoryImpl diagnosisKeysDirectory =
57         new DiagnosisKeysDirectoryImpl(diagnosisKeys, cryptoProvider);
58 
59     IndexDirectory<?> versionDirectory =
60         new IndexDirectoryImpl<>(VERSION_DIRECTORY, __ -> Set.of(version), Object::toString);
61 
62     versionDirectory.addDirectoryToAll(__ -> diagnosisKeysDirectory);
63 
64     java.io.File outputDirectory = new File(outputPath);
65     clearDirectory(outputDirectory);
66     DirectoryImpl root = new DirectoryImpl(outputDirectory);
67     root.addDirectory(new IndexingDecorator<>(versionDirectory));
68 
69     root.prepare(new ImmutableStack<>());
70     root.write();
71     logger.debug("Diagnosis key distribution structure written successfully.");
72   }
73 
74   private static void clearDirectory(File directory) throws IOException {
75     FileUtils.deleteDirectory(directory);
76     directory.mkdirs();
77   }
78 }
79 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/DiagnosisKeyDistributionRunner.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/ExposureConfigurationDistributionRunner.java]
1 package app.coronawarn.server.services.distribution.runner;
2 
3 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.exposureconfig.ExposureConfigurationProvider;
6 import app.coronawarn.server.services.distribution.exposureconfig.UnableToLoadFileException;
7 import app.coronawarn.server.services.distribution.exposureconfig.structure.directory.ExposureConfigurationDirectoryImpl;
8 import app.coronawarn.server.services.distribution.structure.directory.Directory;
9 import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
10 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
11 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
12 import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
13 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
14 import java.io.File;
15 import java.util.Set;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.beans.factory.annotation.Value;
20 import org.springframework.boot.ApplicationArguments;
21 import org.springframework.boot.ApplicationRunner;
22 import org.springframework.core.annotation.Order;
23 import org.springframework.stereotype.Component;
24 
25 @Component
26 @Order(2)
27 /**
28  * Reads the exposure configuration parameters from the respective file in the class path, then
29  * generates and persists the respective exposure configuration bundle.
30  */
31 public class ExposureConfigurationDistributionRunner implements ApplicationRunner {
32 
33   private static final Logger logger =
34       LoggerFactory.getLogger(ExposureConfigurationDistributionRunner.class);
35 
36   @Value("${app.coronawarn.server.services.distribution.version}")
37   private String version;
38 
39   @Value("${app.coronawarn.server.services.distribution.paths.output}")
40   private String outputPath;
41 
42   @Autowired
43   private CryptoProvider cryptoProvider;
44 
45   private static final String VERSION_DIRECTORY = "version";
46 
47   @Override
48   public void run(ApplicationArguments args) {
49     var riskScoreParameters = readExposureConfiguration();
50     IndexDirectory<?> versionDirectory =
51         new IndexDirectoryImpl<>(VERSION_DIRECTORY, __ -> Set.of(version), Object::toString);
52     ExposureConfigurationDirectoryImpl parametersDirectory =
53         new ExposureConfigurationDirectoryImpl(riskScoreParameters, cryptoProvider);
54     Directory root = new DirectoryImpl(new File(outputPath));
55     versionDirectory.addDirectoryToAll(__ -> parametersDirectory);
56     root.addDirectory(new IndexingDecorator<>(versionDirectory));
57     root.prepare(new ImmutableStack<>());
58     root.write();
59     logger.debug("Exposure configuration structure written successfully.");
60   }
61 
62   private RiskScoreParameters readExposureConfiguration() {
63     try {
64       return ExposureConfigurationProvider.readMasterFile();
65     } catch (UnableToLoadFileException e) {
66       logger.error("Could not load exposure configuration parameters", e);
67       throw new RuntimeException(e);
68     }
69   }
70 }
71 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/ExposureConfigurationDistributionRunner.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunner.java]
1 package app.coronawarn.server.services.distribution.runner;
2 
3 import app.coronawarn.server.common.persistence.service.DiagnosisKeyService;
4 import org.slf4j.Logger;
5 import org.slf4j.LoggerFactory;
6 import org.springframework.beans.factory.annotation.Autowired;
7 import org.springframework.beans.factory.annotation.Value;
8 import org.springframework.boot.ApplicationArguments;
9 import org.springframework.boot.ApplicationRunner;
10 import org.springframework.core.annotation.Order;
11 import org.springframework.stereotype.Component;
12 
13 /**
14  * This runner removes any diagnosis keys from the database that were submitted before a configured
15  * threshold of days.
16  */
17 @Component
18 @Order(1)
19 public class RetentionPolicyRunner implements ApplicationRunner {
20 
21   private static final Logger logger = LoggerFactory
22       .getLogger(RetentionPolicyRunner.class);
23 
24   @Autowired
25   private DiagnosisKeyService diagnosisKeyService;
26 
27   @Value("${app.coronawarn.server.services.distribution.retention_days}")
28   private Integer rententionDays;
29 
30   @Override
31   public void run(ApplicationArguments args) {
32     diagnosisKeyService.applyRetentionPolicy(rententionDays);
33 
34     logger.debug("Retention policy applied successfully. Deleted all entries older that {} days.",
35         rententionDays);
36   }
37 }
38 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunner.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3DistributionRunner.java]
1 package app.coronawarn.server.services.distribution.runner;
2 
3 import app.coronawarn.server.services.distribution.objectstore.S3Publisher;
4 import java.io.IOException;
5 import java.nio.file.Path;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8 import org.springframework.beans.factory.annotation.Autowired;
9 import org.springframework.beans.factory.annotation.Value;
10 import org.springframework.boot.ApplicationArguments;
11 import org.springframework.boot.ApplicationRunner;
12 import org.springframework.core.annotation.Order;
13 import org.springframework.stereotype.Component;
14 
15 /**
16  * This runner will sync the base working directory to the S3.
17  */
18 @Component
19 @Order(4)
20 public class S3DistributionRunner implements ApplicationRunner {
21 
22   private Logger logger = LoggerFactory.getLogger(this.getClass());
23 
24   @Value("${app.coronawarn.server.services.distribution.paths.output}")
25   private String workdir;
26 
27   @Autowired
28   private S3Publisher s3Publisher;
29 
30   @Override
31   public void run(ApplicationArguments args) {
32     try {
33       Path pathToDistribute = Path.of(workdir).toAbsolutePath();
34 
35       s3Publisher.publishFolder(pathToDistribute);
36     } catch (IOException | UnsupportedOperationException e) {
37       logger.error("Distribution failed.", e);
38     }
39     logger.debug("Data pushed to CDN successfully.");
40   }
41 }
42 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3DistributionRunner.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/Writable.java]
1 package app.coronawarn.server.services.distribution.structure;
2 
3 import app.coronawarn.server.services.distribution.structure.directory.Directory;
4 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
5 
6 /**
7  * Something that can be written to disk.
8  */
9 public interface Writable {
10 
11   /**
12    * Writes this {@link Writable} to disk.
13    */
14   void write();
15 
16   /**
17    * Returns the name of this {@link Writable}.
18    */
19   String getName();
20 
21   /**
22    * Returns the parent of this {@link Writable}, or {@code null} if it doesn't have a parent.
23    */
24   Directory getParent();
25 
26   /**
27    * Sets the parent of this {@link Writable}.
28    */
29   void setParent(Directory parent);
30 
31   /**
32    * Returns the {@link java.io.File} that this {@link Writable} represents on disk.
33    */
34   java.io.File getFileOnDisk();
35 
36   /**
37    * Does preparation work for this {@link Writable} (e.g. calculate data, setup structures, etc.).
38    */
39   void prepare(ImmutableStack<Object> indices);
40 }
41 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/Writable.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/WritableImpl.java]
1 package app.coronawarn.server.services.distribution.structure;
2 
3 import app.coronawarn.server.services.distribution.structure.directory.Directory;
4 import java.util.Objects;
5 
6 public abstract class WritableImpl implements Writable {
7 
8   private String name;
9   private Directory parent;
10   private java.io.File fileOnDisk;
11 
12   protected WritableImpl(String name) {
13     this.name = name;
14   }
15 
16   protected WritableImpl(java.io.File fileOnDisk) {
17     this.fileOnDisk = fileOnDisk;
18   }
19 
20   @Override
21   public String getName() {
22     return this.name;
23   }
24 
25   @Override
26   public Directory getParent() {
27     return this.parent;
28   }
29 
30   @Override
31   public void setParent(Directory parent) {
32     this.parent = parent;
33   }
34 
35   @Override
36   public java.io.File getFileOnDisk() {
37     return Objects.requireNonNullElseGet(this.fileOnDisk,
38         () -> getParent().getFileOnDisk().toPath().resolve(this.getName()).toFile());
39   }
40 }
41 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/WritableImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/Directory.java]
1 package app.coronawarn.server.services.distribution.structure.directory;
2 
3 import app.coronawarn.server.services.distribution.structure.Writable;
4 import app.coronawarn.server.services.distribution.structure.file.File;
5 import java.util.Set;
6 
7 /**
8  * A {@link Writable} containing {@link File files} and further {@link Directory directories}.
9  */
10 public interface Directory extends Writable {
11 
12   /**
13    * Adds a {@link File file} to the {@link DirectoryImpl#getFiles files} of this {@link
14    * Directory}.
15    */
16   void addFile(File file);
17 
18   /**
19    * Returns all {@link File files} contained in this {@link Directory}.
20    */
21   Set<File> getFiles();
22 
23   /**
24    * Adds a {@link Directory directory} to the {@link DirectoryImpl#getDirectories directories} of
25    * this {@link Directory}.
26    */
27   void addDirectory(Directory directory);
28 
29 
30   /**
31    * Returns all {@link Directory directories} contained in this {@link Directory}.
32    */
33   Set<Directory> getDirectories();
34 }
35 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/Directory.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.structure.directory;
2 
3 import app.coronawarn.server.services.distribution.structure.WritableImpl;
4 import app.coronawarn.server.services.distribution.structure.file.File;
5 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
6 import java.util.HashSet;
7 import java.util.Set;
8 
9 /**
10  * Implementation of {@link Directory} that interfaces with {@link java.io.File Files} on disk.
11  */
12 public class DirectoryImpl extends WritableImpl implements Directory {
13 
14   private final Set<File> files = new HashSet<>();
15   private final Set<Directory> directories = new HashSet<>();
16 
17   /**
18    * A root {@link DirectoryImpl} representing an already existing directory on disk.
19    *
20    * @param file The {@link File File} that this {@link DirectoryImpl} represents on disk.
21    */
22   public DirectoryImpl(java.io.File file) {
23     super(file);
24   }
25 
26   /**
27    * A {@link DirectoryImpl} that does not yet represent an already existing directory on disk, but
28    * one that shall be created on disk when calling {@link DirectoryImpl#write}. A parent needs to
29    * be defined by calling {@link DirectoryImpl#setParent}, before writing can succeed.
30    *
31    * @param name The name that this directory should have on disk.
32    */
33   public DirectoryImpl(String name) {
34     super(name);
35   }
36 
37   @Override
38   public void addFile(File file) {
39     this.files.add(file);
40     file.setParent(this);
41   }
42 
43   @Override
44   public Set<File> getFiles() {
45     return this.files;
46   }
47 
48   @Override
49   public void addDirectory(Directory directory) {
50     this.directories.add(directory);
51     directory.setParent(this);
52   }
53 
54   @Override
55   public Set<Directory> getDirectories() {
56     return this.directories;
57   }
58 
59   @Override
60   public void prepare(ImmutableStack<Object> indices) {
61     this.prepareFiles(indices);
62     this.prepareDirectories(indices);
63   }
64 
65   private void prepareDirectories(ImmutableStack<Object> indices) {
66     this.directories.forEach(directory -> directory.prepare(indices));
67   }
68 
69   private void prepareFiles(ImmutableStack<Object> indices) {
70     this.files.forEach(file -> file.prepare(indices));
71   }
72 
73   /**
74    * Writes this {@link DirectoryImpl} and all of its {@link DirectoryImpl#files} and {@link
75    * DirectoryImpl#directories} to disk.
76    */
77   @Override
78   public void write() {
79     this.writeOwnDirectory();
80     this.writeDirectories();
81     this.writeFiles();
82   }
83 
84   private void writeOwnDirectory() {
85     java.io.File file = this.getFileOnDisk();
86     file.mkdirs();
87   }
88 
89   private void writeDirectories() {
90     this.directories.forEach(Directory::write);
91   }
92 
93   private void writeFiles() {
94     this.files.forEach(File::write);
95   }
96 }
97 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectory.java]
1 package app.coronawarn.server.services.distribution.structure.directory;
2 
3 import app.coronawarn.server.services.distribution.structure.functional.DirectoryFunction;
4 import app.coronawarn.server.services.distribution.structure.functional.FileFunction;
5 import app.coronawarn.server.services.distribution.structure.functional.Formatter;
6 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
7 import java.util.Set;
8 import java.util.Stack;
9 
10 /**
11  * A meta directory that maps its on-disk subdirectories to some list of elements. This list of
12  * elements is determined by a {@link FileFunction}.
13  *
14  * @param <T> The type of the elements in the index.
15  */
16 public interface IndexDirectory<T> extends Directory {
17 
18   /**
19    * Adds a file under the name {@code name}, whose content is calculated by the {@code
20    * fileFunction} to each one of the directories created from the index. The {@code fileFunction}
21    * calculates the file content from a {@link java.util.Stack} of parent {@link IndexDirectoryImpl}
22    * indices. File content calculation happens on {@link DirectoryImpl#write}.
23    *
24    * @param fileFunction A function that can calculate the content of the file, based on
25    */
26   void addFileToAll(FileFunction fileFunction);
27 
28   /**
29    * Adds a {@link DirectoryImpl} to each one of the directories created from the index. Analogous
30    * to {@link IndexDirectory#addFileToAll}.
31    */
32   void addDirectoryToAll(DirectoryFunction directoryFunction);
33 
34   /**
35    * Calls the {@link app.coronawarn.server.services.distribution.structure.functional.IndexFunction}
36    * with the {@code indices} to calculate and return the elements of the index of this {@link
37    * IndexDirectory}.
38    *
39    * @param indices A {@link Stack} of parameters from all {@link IndexDirectory IndexDirectories}
40    *                further up in the hierarchy. The Stack may contain different types, depending on
41    *                the types {@code T} of {@link IndexDirectory IndexDirectories} further up in the
42    *                hierarchy.
43    */
44   Set<T> getIndex(ImmutableStack<Object> indices);
45 
46   /**
47    * Returns the function used to format elements of the index (e.g. for writing to disk).
48    */
49   Formatter<T> getIndexFormatter();
50 }
51 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectory.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryImpl.java]
1 package app.coronawarn.server.services.distribution.structure.directory;
2 
3 import app.coronawarn.server.services.distribution.structure.file.File;
4 import app.coronawarn.server.services.distribution.structure.functional.DirectoryFunction;
5 import app.coronawarn.server.services.distribution.structure.functional.FileFunction;
6 import app.coronawarn.server.services.distribution.structure.functional.Formatter;
7 import app.coronawarn.server.services.distribution.structure.functional.IndexFunction;
8 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
9 import java.util.HashSet;
10 import java.util.Set;
11 import java.util.Stack;
12 
13 public class IndexDirectoryImpl<T> extends DirectoryImpl implements IndexDirectory<T> {
14 
15   // Files to be written into every directory created through the index
16   private final Set<FileFunction> metaFiles = new HashSet<>();
17   // Directories to be written into every directory created through the index
18   private final Set<DirectoryFunction> metaDirectories = new HashSet<>();
19   private final IndexFunction<T> indexFunction;
20   private final Formatter<T> indexFormatter;
21 
22   /**
23    * Constructor.
24    *
25    * @param name           The name that this directory should have on disk.
26    * @param indexFunction  An {@link IndexFunction} that calculates the index of this {@link
27    *                       IndexDirectoryImpl} from a {@link java.util.Stack} of parent {@link
28    *                       IndexDirectoryImpl} indices. The top element of the stack is from the
29    *                       closest {@link IndexDirectoryImpl} in the parent chain.
30    * @param indexFormatter A {@link Formatter} used to format the directory name for each index
31    *                       element returned by the {@link IndexDirectoryImpl#indexFunction}.
32    */
33   public IndexDirectoryImpl(String name, IndexFunction<T> indexFunction,
34       Formatter<T> indexFormatter) {
35     super(name);
36     this.indexFunction = indexFunction;
37     this.indexFormatter = indexFormatter;
38   }
39 
40   @Override
41   public Formatter<T> getIndexFormatter() {
42     return this.indexFormatter;
43   }
44 
45   @Override
46   public Set<T> getIndex(ImmutableStack<Object> indices) {
47     return this.indexFunction.apply(indices);
48   }
49 
50   @Override
51   public void addFileToAll(FileFunction fileFunction) {
52     this.metaFiles.add(fileFunction);
53   }
54 
55   @Override
56   public void addDirectoryToAll(DirectoryFunction directoryFunction) {
57     this.metaDirectories.add(directoryFunction);
58   }
59 
60   /**
61    * Creates a new subdirectory for every element of the {@link IndexDirectory#getIndex index} and
62    * writes {@link IndexDirectory#addFileToAll files} and {@link IndexDirectory#addDirectory
63    * directories} to those. The respective element of the index will be pushed onto the Stack for
64    * subsequent {@link app.coronawarn.server.services.distribution.structure.Writable#prepare} calls
65    * on those files and directories.
66    *
67    * @param indices A {@link Stack} of parameters from all {@link IndexDirectory IndexDirectories}
68    *                further up in the hierarchy. The Stack may contain different types, depending on
69    *                the types {@code T} of {@link IndexDirectory IndexDirectories} further up in the
70    *                hierarchy.
71    */
72   @Override
73   public void prepare(ImmutableStack<Object> indices) {
74     super.prepare(indices);
75     this.prepareIndex(indices);
76   }
77 
78   private void prepareIndex(ImmutableStack<Object> indices) {
79     this.getIndex(indices).forEach(currentIndex -> {
80       ImmutableStack<Object> newIndices = indices.push(currentIndex);
81       Directory subDirectory = makeSubDirectory(currentIndex);
82       prepareMetaFiles(newIndices, subDirectory);
83       prepareMetaDirectories(newIndices, subDirectory);
84     });
85   }
86 
87   private Directory makeSubDirectory(T index) {
88     Directory subDirectory = new DirectoryImpl(this.indexFormatter.apply(index).toString());
89     this.addDirectory(subDirectory);
90     return subDirectory;
91   }
92 
93   private void prepareMetaFiles(ImmutableStack<Object> indices, Directory target) {
94     this.metaFiles.forEach(metaFileFunction -> {
95       File newFile = metaFileFunction.apply(indices);
96       target.addFile(newFile);
97       newFile.prepare(indices);
98     });
99   }
100 
101   private void prepareMetaDirectories(ImmutableStack<Object> indices, Directory target) {
102     this.metaDirectories.forEach(metaDirectoryFunction -> {
103       Directory newDirectory = metaDirectoryFunction.apply(indices);
104       target.addDirectory(newDirectory);
105       newDirectory.prepare(indices);
106     });
107   }
108 }
109 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/DirectoryDecorator.java]
1 package app.coronawarn.server.services.distribution.structure.directory.decorator;
2 
3 import app.coronawarn.server.services.distribution.structure.Writable;
4 import app.coronawarn.server.services.distribution.structure.directory.Directory;
5 import app.coronawarn.server.services.distribution.structure.file.File;
6 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
7 import java.util.Set;
8 
9 /**
10  * Decorates a {@link Directory} (e.g. to modify its files, subdirectories, etc.) on {@link
11  * Writable#prepare}. This class proxies all function calls to the {@link Directory} it contains.
12  */
13 public abstract class DirectoryDecorator implements Directory {
14 
15   private final Directory directory;
16 
17   protected DirectoryDecorator(Directory directory) {
18     this.directory = directory;
19   }
20 
21   @Override
22   public void prepare(ImmutableStack<Object> indices) {
23     this.directory.prepare(indices);
24   }
25 
26   @Override
27   public void addFile(File file) {
28     this.directory.addFile(file);
29   }
30 
31   @Override
32   public Set<File> getFiles() {
33     return this.directory.getFiles();
34   }
35 
36   @Override
37   public void addDirectory(Directory directory) {
38     this.directory.addDirectory(directory);
39   }
40 
41   @Override
42   public Set<Directory> getDirectories() {
43     return this.directory.getDirectories();
44   }
45 
46   @Override
47   public void write() {
48     this.directory.write();
49   }
50 
51   @Override
52   public String getName() {
53     return this.directory.getName();
54   }
55 
56   @Override
57   public Directory getParent() {
58     return this.directory.getParent();
59   }
60 
61   @Override
62   public void setParent(Directory parent) {
63     this.directory.setParent(parent);
64   }
65 
66   @Override
67   public java.io.File getFileOnDisk() {
68     return this.directory.getFileOnDisk();
69   }
70 }
71 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/DirectoryDecorator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/IndexingDecorator.java]
1 package app.coronawarn.server.services.distribution.structure.directory.decorator;
2 
3 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
4 import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
5 import app.coronawarn.server.services.distribution.structure.file.FileImpl;
6 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
7 import java.util.List;
8 import java.util.Set;
9 import java.util.stream.Collectors;
10 import org.json.simple.JSONArray;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 
14 /**
15  * A {@link DirectoryDecorator} that writes a file called {@code "index"}, containing a JSON array
16  * containing all elements returned {@link IndexDirectoryImpl#getIndex}, formatted with the {@link
17  * IndexDirectoryImpl#getIndexFormatter} on {@link app.coronawarn.server.services.distribution.structure.Writable#prepare}.
18  */
19 public class IndexingDecorator<T> extends DirectoryDecorator {
20 
21   private static final String INDEX_FILE_NAME = "index";
22 
23   private static final Logger logger = LoggerFactory.getLogger(IndexingDecorator.class);
24   final IndexDirectory<T> directory;
25 
26   public IndexingDecorator(IndexDirectory<T> directory) {
27     super(directory);
28     this.directory = directory;
29   }
30 
31   /**
32    * See {@link IndexingDecorator} class documentation.
33    */
34   @Override
35   public void prepare(ImmutableStack<Object> indices) {
36     logger.debug("Indexing {}", this.getFileOnDisk().getPath());
37     Set<T> index = this.directory.getIndex(indices);
38     JSONArray array = new JSONArray();
39     List<?> elements = index.stream()
40         .map(this.directory.getIndexFormatter())
41         .collect(Collectors.toList());
42     array.addAll(elements);
43     this.addFile(new FileImpl(INDEX_FILE_NAME, array.toJSONString().getBytes()));
44     super.prepare(indices);
45   }
46 }
47 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/IndexingDecorator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/File.java]
1 package app.coronawarn.server.services.distribution.structure.file;
2 
3 import app.coronawarn.server.services.distribution.structure.Writable;
4 
5 /**
6  * A {@link Writable} containing some bytes.
7  */
8 public interface File extends Writable {
9 
10   /**
11    * Returns the bytes contained by this {@link File}.
12    */
13   byte[] getBytes();
14 
15   /**
16    * Sets the bytes to be contained by this {@link File}.
17    */
18   void setBytes(byte[] bytes);
19 }
20 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/File.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/FileImpl.java]
1 package app.coronawarn.server.services.distribution.structure.file;
2 
3 import app.coronawarn.server.services.distribution.io.IO;
4 import app.coronawarn.server.services.distribution.structure.Writable;
5 import app.coronawarn.server.services.distribution.structure.WritableImpl;
6 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
7 
8 /**
9  * Implementation of {@link File} that interfaces with {@link java.io.File Files} on disk.
10  */
11 public class FileImpl extends WritableImpl implements File {
12 
13   private byte[] bytes;
14 
15   public FileImpl(String name, byte[] bytes) {
16     super(name);
17     this.bytes = bytes;
18   }
19 
20   /**
21    * Creates a {@link java.io.File} with name {@link Writable#getName} on disk and writes the {@link
22    * File#getBytes bytes} of this {@link File} into the {@link java.io.File} to disk.
23    */
24   @Override
25   public void write() {
26     IO.makeFile(this.getParent().getFileOnDisk(), this.getName());
27     IO.writeBytesToFile(this.getBytes(), this.getFileOnDisk());
28   }
29 
30   @Override
31   public byte[] getBytes() {
32     return this.bytes;
33   }
34 
35   @Override
36   public void setBytes(byte[] bytes) {
37     this.bytes = bytes;
38   }
39 
40   /**
41    * Does nothing.
42    */
43   @Override
44   public void prepare(ImmutableStack<Object> indices) {
45   }
46 }
47 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/FileImpl.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/FileDecorator.java]
1 package app.coronawarn.server.services.distribution.structure.file.decorator;
2 
3 import app.coronawarn.server.services.distribution.structure.Writable;
4 import app.coronawarn.server.services.distribution.structure.directory.Directory;
5 import app.coronawarn.server.services.distribution.structure.file.File;
6 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
7 
8 /**
9  * Decorates a {@link File} (e.g. to modify its content) on {@link Writable#prepare}. This class
10  * proxies all function calls to the {@link File} it contains.
11  */
12 public abstract class FileDecorator implements File {
13 
14   private final File file;
15 
16   protected FileDecorator(File file) {
17     this.file = file;
18   }
19 
20   @Override
21   public void prepare(ImmutableStack<Object> indices) {
22     this.file.prepare(indices);
23   }
24 
25   @Override
26   public byte[] getBytes() {
27     return this.file.getBytes();
28   }
29 
30   @Override
31   public void setBytes(byte[] bytes) {
32     this.file.setBytes(bytes);
33   }
34 
35   @Override
36   public void write() {
37     this.file.write();
38   }
39 
40   @Override
41   public String getName() {
42     return this.file.getName();
43   }
44 
45   @Override
46   public Directory getParent() {
47     return this.file.getParent();
48   }
49 
50   @Override
51   public void setParent(Directory parent) {
52     this.file.setParent(parent);
53   }
54 
55   @Override
56   public java.io.File getFileOnDisk() {
57     return this.file.getFileOnDisk();
58   }
59 }
60 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/FileDecorator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/SigningDecorator.java]
1 package app.coronawarn.server.services.distribution.structure.file.decorator;
2 
3 import app.coronawarn.server.common.protocols.internal.SignedPayload;
4 import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
5 import app.coronawarn.server.services.distribution.structure.file.File;
6 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
7 import com.google.protobuf.ByteString;
8 import java.security.GeneralSecurityException;
9 import java.security.PrivateKey;
10 import java.security.Signature;
11 import java.security.cert.Certificate;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14 
15 /**
16  * A {@link FileDecorator} that will convert the contents of its {@link File} into a {@link
17  * app.coronawarn.server.common.protocols.internal.SignedPayload}.
18  */
19 public class SigningDecorator extends FileDecorator {
20 
21   private static final String SIGNATURE_ALGORITHM = "Ed25519";
22   private static final String SECURITY_PROVIDER = "BC";
23 
24   private static final Logger logger = LoggerFactory.getLogger(SigningDecorator.class);
25   private final CryptoProvider cryptoProvider;
26 
27   public SigningDecorator(File file, CryptoProvider cryptoProvider) {
28     super(file);
29     this.cryptoProvider = cryptoProvider;
30   }
31 
32   /**
33    * See {@link SigningDecorator} class documentation.
34    */
35   @Override
36   public void prepare(ImmutableStack<Object> indices) {
37     super.prepare(indices);
38     logger.debug("Signing {}", this.getFileOnDisk().getPath());
39     SignedPayload signedPayload = sign(this.getBytes(), cryptoProvider.getPrivateKey(),
40         cryptoProvider.getCertificate());
41     this.setBytes(signedPayload.toByteArray());
42   }
43 
44   private static SignedPayload sign(byte[] payload, PrivateKey privateKey,
45       Certificate certificate) {
46     try {
47       Signature payloadSignature = Signature.getInstance(SIGNATURE_ALGORITHM, SECURITY_PROVIDER);
48       payloadSignature.initSign(privateKey);
49       payloadSignature.update(payload);
50       return SignedPayload.newBuilder()
51           .setCertificateChain(ByteString.copyFrom(certificate.getEncoded()))
52           .setSignature(ByteString.copyFrom(payloadSignature.sign()))
53           .setPayload(ByteString.copyFrom(payload))
54           .build();
55     } catch (GeneralSecurityException e) {
56       logger.error("Exception during payload signing.", e);
57       throw new RuntimeException(e);
58     }
59   }
60 }
61 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/SigningDecorator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/CheckedFunction.java]
1 package app.coronawarn.server.services.distribution.structure.functional;
2 
3 import java.util.function.Function;
4 
5 /**
6  * Convert checked exceptions to unchecked exceptions in Functions.
7  */
8 @FunctionalInterface
9 public interface CheckedFunction<T, R, E extends Exception> {
10 
11   R apply(T t) throws E;
12 
13   static <T, R> Function<T, R> uncheckedFunction(
14       CheckedFunction<T, R, ? extends Exception> function) {
15     return input -> {
16       try {
17         return function.apply(input);
18       } catch (Exception e) {
19         throw new RuntimeException(e);
20       }
21     };
22   }
23 }
24 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/CheckedFunction.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/DirectoryFunction.java]
1 package app.coronawarn.server.services.distribution.structure.functional;
2 
3 import app.coronawarn.server.services.distribution.structure.directory.Directory;
4 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
5 import java.util.function.Function;
6 
7 /**
8  * A {@code Function<Stack<Object>, Directory>}.
9  */
10 @FunctionalInterface
11 public interface DirectoryFunction extends Function<ImmutableStack<Object>, Directory> {
12 
13   Directory apply(ImmutableStack<Object> t);
14 }
15 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/DirectoryFunction.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/FileFunction.java]
1 package app.coronawarn.server.services.distribution.structure.functional;
2 
3 import app.coronawarn.server.services.distribution.structure.file.File;
4 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
5 import java.util.function.Function;
6 
7 /**
8  * A {@code Function<Stack<Object>, File>}.
9  */
10 @FunctionalInterface
11 public interface FileFunction extends Function<ImmutableStack<Object>, File> {
12 
13   File apply(ImmutableStack<Object> t);
14 }
15 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/FileFunction.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/Formatter.java]
1 package app.coronawarn.server.services.distribution.structure.functional;
2 
3 import java.util.function.Function;
4 
5 /**
6  * A {@code Function<T, Object>}.
7  *
8  * @param <T> The type of the elements to format.
9  */
10 @FunctionalInterface
11 public interface Formatter<T> extends Function<T, Object> {
12 
13   Object apply(T t);
14 }
15 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/Formatter.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/IndexFunction.java]
1 package app.coronawarn.server.services.distribution.structure.functional;
2 
3 import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
4 import java.util.Set;
5 import java.util.function.Function;
6 
7 /**
8  * A {@code Function<Stack<Object>, List<T>>}.
9  *
10  * @param <T> The type of the index elements.
11  */
12 @FunctionalInterface
13 public interface IndexFunction<T> extends Function<ImmutableStack<Object>, Set<T>> {
14 
15   Set<T> apply(ImmutableStack<Object> t);
16 }
17 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/IndexFunction.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/util/ImmutableStack.java]
1 package app.coronawarn.server.services.distribution.structure.util;
2 
3 import java.util.Stack;
4 
5 public class ImmutableStack<T> {
6 
7   private Stack<T> stack;
8 
9   public ImmutableStack() {
10     this.stack = new Stack<>();
11   }
12 
13   public ImmutableStack(ImmutableStack<T> other) {
14     this.stack = (Stack<T>)other.stack.clone();
15   }
16 
17   public ImmutableStack<T> push(T item) {
18     ImmutableStack<T> clone = new ImmutableStack<>(this);
19     clone.stack.push(item);
20     return clone;
21   }
22 
23   public ImmutableStack<T> pop() {
24     ImmutableStack<T> clone = new ImmutableStack<>(this);
25     clone.stack.pop();
26     return clone;
27   }
28 
29   public T peek() {
30     return this.stack.peek();
31   }
32 }
33 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/util/ImmutableStack.java]
[start of services/distribution/src/main/resources/application.properties]
1 logging.level.org.springframework.web=INFO
2 logging.level.app.coronawarn=INFO
3 spring.main.web-application-type=NONE
4 
5 app.coronawarn.server.services.distribution.retention_days=14
6 app.coronawarn.server.services.distribution.version=v1
7 app.coronawarn.server.services.distribution.paths.output=out
8 app.coronawarn.server.services.distribution.paths.privatekey=classpath:certificates/client/private.pem
9 app.coronawarn.server.services.distribution.paths.certificate=classpath:certificates/chain/certificate.crt
10 
11 spring.flyway.enabled=false
12 spring.jpa.hibernate.ddl-auto=validate
13 
[end of services/distribution/src/main/resources/application.properties]
</code>

Here is an example of a patch file. It consists of changes to the code base. It specifies the file names, the line numbers of each change, and the removed and added lines. A single patch file can contain changes to multiple files.
<patch>
--- a/file.py
+++ b/file.py
@@ -1,27 +1,35 @@
 def euclidean(a, b):
-    while b:
-        a, b = b, a % b
-    return a
+    if b == 0:
+        return a
+    return euclidean(b, a % b)
 
 
 def bresenham(x0, y0, x1, y1):
     points = []
     dx = abs(x1 - x0)
     dy = abs(y1 - y0)
-    sx = 1 if x0 < x1 else -1
-    sy = 1 if y0 < y1 else -1
-    err = dx - dy
+    x, y = x0, y0
+    sx = -1 if x0 > x1 else 1
+    sy = -1 if y0 > y1 else 1
 
-    while True:
-        points.append((x0, y0))
-        if x0 == x1 and y0 == y1:
-            break
-        e2 = 2 * err
-        if e2 > -dy:
+    if dx > dy:
+        err = dx / 2.0
+        while x != x1:
+            points.append((x, y))
             err -= dy
-            x0 += sx
-        if e2 < dx:
-            err += dx
-            y0 += sy
+            if err < 0:
+                y += sy
+                err += dx
+            x += sx
+    else:
+        err = dy / 2.0
+        while y != y1:
+            points.append((x, y))
+            err -= dx
+            if err < 0:
+                x += sx
+                err += dy
+            y += sy
 
+    points.append((x, y))
     return points
</patch>

I need you to solve the provded issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the format shown above.
Respond below:


DiagnosisKeyDistributionRunner deletes exposure configuration
``DiagnosisKeyDistributionRunner`` deletes the files written by ``ExposureConfigurationDistributionRunner`` before they are published.

<patch>
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/crypto/CryptoProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java
similarity index 89%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/crypto/CryptoProvider.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/crypto/CryptoProvider.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.crypto;
+package app.coronawarn.server.services.distribution.assembly.component;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -22,7 +22,7 @@
 import org.springframework.stereotype.Component;
 
 /**
- * Wrapper class for a {@link CryptoProvider#getPrivateKey() private key} and a {@link
+ * Wrapper component for a {@link CryptoProvider#getPrivateKey() private key} and a {@link
  * CryptoProvider#getCertificate()} certificate} from the application properties.
  */
 @Component
@@ -30,10 +30,10 @@ public class CryptoProvider {
 
   private static final Logger logger = LoggerFactory.getLogger(CryptoProvider.class);
 
-  @Value("${app.coronawarn.server.services.distribution.paths.privatekey}")
+  @Value("${services.distribution.paths.privatekey}")
   private String privateKeyPath;
 
-  @Value("${app.coronawarn.server.services.distribution.paths.certificate}")
+  @Value("${services.distribution.paths.certificate}")
   private String certificatePath;
 
   @Autowired
@@ -72,10 +72,10 @@ private static Certificate getCertificateFromBytes(final byte[] bytes)
    * Reads and returns the {@link PrivateKey} configured in the application properties.
    */
   public PrivateKey getPrivateKey() {
-    if (this.privateKey == null) {
+    if (privateKey == null) {
       Resource privateKeyResource = resourceLoader.getResource(privateKeyPath);
       try (InputStream privateKeyStream = privateKeyResource.getInputStream()) {
-        this.privateKey = getPrivateKeyFromStream(privateKeyStream);
+        privateKey = getPrivateKeyFromStream(privateKeyStream);
       } catch (IOException e) {
         logger.error("Failed to load private key from {}", privateKeyPath, e);
         throw new RuntimeException(e);
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CwaApiStructureProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CwaApiStructureProvider.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CwaApiStructureProvider.java
@@ -0,0 +1,35 @@
+package app.coronawarn.server.services.distribution.assembly.component;
+
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Assembles the content underneath the {@code /version} path of the CWA API.
+ */
+@Component
+public class CwaApiStructureProvider {
+
+  private final String VERSION_DIRECTORY = "version";
+  private final String VERSION_V1 = "v1";
+
+  @Autowired
+  private ExposureConfigurationStructureProvider exposureConfigurationStructureProvider;
+
+  @Autowired
+  private DiagnosisKeysStructureProvider diagnosisKeysStructureProvider;
+
+  public Directory getDirectory() {
+    IndexDirectory<?> versionDirectory =
+        new IndexDirectoryImpl<>(VERSION_DIRECTORY, __ -> Set.of(VERSION_V1), Object::toString);
+
+    versionDirectory.addDirectoryToAll(__ -> exposureConfigurationStructureProvider.getExposureConfiguration());
+    versionDirectory.addDirectoryToAll(__ -> diagnosisKeysStructureProvider.getDiagnosisKeys());
+
+    return new IndexingDecorator<>(versionDirectory);
+  }
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java
@@ -0,0 +1,37 @@
+package app.coronawarn.server.services.distribution.assembly.component;
+
+import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
+import app.coronawarn.server.common.persistence.service.DiagnosisKeyService;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.DiagnosisKeysDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import java.util.Collection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Retrieves stored diagnosis keys and builds a {@link DiagnosisKeysDirectoryImpl} with them.
+ */
+@Component
+public class DiagnosisKeysStructureProvider {
+
+  private static final Logger logger = LoggerFactory.getLogger(DiagnosisKeysStructureProvider.class);
+
+  @Autowired
+  private DiagnosisKeyService diagnosisKeyService;
+
+  @Autowired
+  private CryptoProvider cryptoProvider;
+
+  public Directory getDiagnosisKeys() {
+    Collection<DiagnosisKey> diagnosisKeys = readDiagnosisKeys();
+    return new DiagnosisKeysDirectoryImpl(diagnosisKeys, cryptoProvider);
+  }
+
+  private Collection<DiagnosisKey> readDiagnosisKeys() {
+    logger.debug("Querying diagnosis keys from the database...");
+    return diagnosisKeyService.getDiagnosisKeys();
+  }
+
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/ExposureConfigurationStructureProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/ExposureConfigurationStructureProvider.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/ExposureConfigurationStructureProvider.java
@@ -0,0 +1,40 @@
+package app.coronawarn.server.services.distribution.assembly.component;
+
+import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.ExposureConfigurationProvider;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.UnableToLoadFileException;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.structure.directory.ExposureConfigurationDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Reads the exposure configuration parameters from the respective file in the class path and builds
+ * a {@link ExposureConfigurationDirectoryImpl} with them.
+ */
+@Component
+public class ExposureConfigurationStructureProvider {
+
+  private static final Logger logger = LoggerFactory
+      .getLogger(ExposureConfigurationStructureProvider.class);
+
+  @Autowired
+  private CryptoProvider cryptoProvider;
+
+  public Directory getExposureConfiguration() {
+    var riskScoreParameters = readExposureConfiguration();
+    return new ExposureConfigurationDirectoryImpl(riskScoreParameters, cryptoProvider);
+  }
+
+  private RiskScoreParameters readExposureConfiguration() {
+    logger.debug("Reading exposure configuration...");
+    try {
+      return ExposureConfigurationProvider.readMasterFile();
+    } catch (UnableToLoadFileException e) {
+      logger.error("Could not load exposure configuration parameters", e);
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/OutputDirectoryProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/OutputDirectoryProvider.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/OutputDirectoryProvider.java
@@ -0,0 +1,37 @@
+package app.coronawarn.server.services.distribution.assembly.component;
+
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * Creates and clears a {@link Directory} on disk, which is defined by the application properties.
+ */
+@Component
+public class OutputDirectoryProvider {
+
+  private static final Logger logger = LoggerFactory.getLogger(OutputDirectoryProvider.class);
+
+  @Value("${services.distribution.paths.output}")
+  private String outputPath;
+
+  public Directory getDirectory() {
+    return new DirectoryImpl(getFileOnDisk());
+  }
+
+  public java.io.File getFileOnDisk() {
+    return new java.io.File(outputPath);
+  }
+
+  public void clear() throws IOException {
+    logger.debug("Clearing output directory...");
+    java.io.File outputDirectory = getFileOnDisk();
+    FileUtils.deleteDirectory(outputDirectory);
+    outputDirectory.mkdirs();
+  }
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java
similarity index 55%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectoryImpl.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory.decorator.DateAggregatingDecorator;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator.DateAggregatingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.time.LocalDate;
 import java.util.Collection;
 import java.util.Set;
@@ -17,8 +17,8 @@ public class DiagnosisKeysCountryDirectoryImpl extends IndexDirectoryImpl<String
   private static final String COUNTRY_DIRECTORY = "country";
   private static final String COUNTRY = "DE";
 
-  private Collection<DiagnosisKey> diagnosisKeys;
-  private CryptoProvider cryptoProvider;
+  private final Collection<DiagnosisKey> diagnosisKeys;
+  private final CryptoProvider cryptoProvider;
 
   public DiagnosisKeysCountryDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
       CryptoProvider cryptoProvider) {
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java
similarity index 59%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectoryImpl.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.diagnosiskeys.util.DateTime;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -18,14 +18,14 @@ public class DiagnosisKeysDateDirectoryImpl extends IndexDirectoryImpl<LocalDate
   private static final String DATE_DIRECTORY = "date";
   private static final DateTimeFormatter ISO8601 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
 
-  private Collection<DiagnosisKey> diagnosisKeys;
-  private CryptoProvider cryptoProvider;
+  private final Collection<DiagnosisKey> diagnosisKeys;
+  private final CryptoProvider cryptoProvider;
 
   public DiagnosisKeysDateDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
       CryptoProvider cryptoProvider) {
     super(DATE_DIRECTORY, __ -> DateTime.getDates(diagnosisKeys), ISO8601::format);
-    this.diagnosisKeys = diagnosisKeys;
     this.cryptoProvider = cryptoProvider;
+    this.diagnosisKeys = diagnosisKeys;
   }
 
   @Override
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java
similarity index 69%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryImpl.java
@@ -1,12 +1,12 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.Collection;
 
 /**
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java
similarity index 61%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectoryImpl.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.diagnosiskeys.structure.file.HourFileImpl;
-import app.coronawarn.server.services.distribution.diagnosiskeys.util.DateTime;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.file.HourFileImpl;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.decorator.SigningDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Collection;
@@ -16,9 +16,9 @@ public class DiagnosisKeysHourDirectoryImpl extends IndexDirectoryImpl<LocalDate
 
   private static final String HOUR_DIRECTORY = "hour";
 
-  private Collection<DiagnosisKey> diagnosisKeys;
-  private LocalDate currentDate;
-  private CryptoProvider cryptoProvider;
+  private final Collection<DiagnosisKey> diagnosisKeys;
+  private final LocalDate currentDate;
+  private final CryptoProvider cryptoProvider;
 
   public DiagnosisKeysHourDirectoryImpl(Collection<DiagnosisKey> diagnosisKeys,
       LocalDate currentDate, CryptoProvider cryptoProvider) {
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java
similarity index 77%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/DateAggregatingDecorator.java
@@ -1,16 +1,16 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory.decorator;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator;
 
 import app.coronawarn.server.common.protocols.internal.FileBucket;
 import app.coronawarn.server.common.protocols.internal.SignedPayload;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.Writable;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.DirectoryDecorator;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
-import app.coronawarn.server.services.distribution.structure.functional.CheckedFunction;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.DirectoryDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.decorator.SigningDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.CheckedFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/file/HourFileImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/HourFileImpl.java
similarity index 86%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/file/HourFileImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/HourFileImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/file/HourFileImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/HourFileImpl.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.file;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.file;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
 import app.coronawarn.server.common.protocols.external.exposurenotification.File;
 import app.coronawarn.server.common.protocols.external.exposurenotification.Key;
 import app.coronawarn.server.common.protocols.internal.FileBucket;
-import app.coronawarn.server.services.distribution.diagnosiskeys.util.Batch;
-import app.coronawarn.server.services.distribution.diagnosiskeys.util.DateTime;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.Batch;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import com.google.protobuf.ByteString;
 import java.time.Instant;
 import java.time.LocalDateTime;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Batch.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/Batch.java
similarity index 97%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Batch.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/Batch.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Batch.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/Batch.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.util;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util;
 
 import app.coronawarn.server.common.protocols.external.exposurenotification.File;
 import app.coronawarn.server.common.protocols.external.exposurenotification.Header;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTime.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java
similarity index 95%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTime.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTime.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.util;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
 import java.time.LocalDate;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Maths.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/Maths.java
similarity index 64%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Maths.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/Maths.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/Maths.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/Maths.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.util;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util;
 
 public class Maths {
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationProvider.java
similarity index 92%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProvider.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationProvider.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProvider.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationProvider.java
@@ -1,8 +1,8 @@
-package app.coronawarn.server.services.distribution.exposureconfig;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig;
 
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters.Builder;
-import app.coronawarn.server.services.distribution.exposureconfig.parsing.YamlConstructorForProtoBuf;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.parsing.YamlConstructorForProtoBuf;
 import java.io.IOException;
 import java.io.InputStream;
 import org.springframework.core.io.ClassPathResource;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/UnableToLoadFileException.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/UnableToLoadFileException.java
similarity index 79%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/UnableToLoadFileException.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/UnableToLoadFileException.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/UnableToLoadFileException.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/UnableToLoadFileException.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig;
 
 /**
  * The file could not be loaded/parsed correctly.
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/parsing/YamlConstructorForProtoBuf.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/parsing/YamlConstructorForProtoBuf.java
similarity index 85%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/parsing/YamlConstructorForProtoBuf.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/parsing/YamlConstructorForProtoBuf.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/parsing/YamlConstructorForProtoBuf.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/parsing/YamlConstructorForProtoBuf.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.parsing;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.parsing;
 
 import java.util.Arrays;
 import org.yaml.snakeyaml.constructor.Constructor;
@@ -20,9 +20,9 @@ public YamlConstructorForProtoBuf() {
     setPropertyUtils(new ProtoBufPropertyUtils());
   }
 
-  private class ProtoBufPropertyUtils extends PropertyUtils {
+  private static class ProtoBufPropertyUtils extends PropertyUtils {
 
-    public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess) {
+    public Property getProperty(Class<?> type, String name, BeanAccess bAccess) {
       return super.getProperty(type, transformToProtoNaming(name), bAccess);
     }
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java
similarity index 69%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/structure/ExposureConfigurationDirectoryImpl.java
@@ -1,12 +1,12 @@
-package app.coronawarn.server.services.distribution.exposureconfig.structure;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.structure;
 
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.decorator.SigningDecorator;
 import java.util.Set;
 
 /**
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java
similarity index 68%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/structure/directory/ExposureConfigurationDirectoryImpl.java
@@ -1,12 +1,12 @@
-package app.coronawarn.server.services.distribution.exposureconfig.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.structure.directory;
 
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.decorator.SigningDecorator;
 import java.util.Set;
 
 /**
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ExposureConfigurationValidator.java
similarity index 93%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidator.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ExposureConfigurationValidator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ExposureConfigurationValidator.java
@@ -1,8 +1,8 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 import app.coronawarn.server.common.protocols.internal.RiskLevel;
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
-import app.coronawarn.server.services.distribution.exposureconfig.validation.WeightValidationError.ErrorType;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.validation.WeightValidationError.ErrorType;
 import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
@@ -19,7 +19,7 @@
  */
 public class ExposureConfigurationValidator {
 
-  private RiskScoreParameters config;
+  private final RiskScoreParameters config;
 
   private ValidationResult errors;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ParameterSpec.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ParameterSpec.java
similarity index 85%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ParameterSpec.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ParameterSpec.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ParameterSpec.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ParameterSpec.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 /**
  * Definition of the spec according to Apple/Google:
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/RiskLevelValidationError.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/RiskLevelValidationError.java
similarity index 93%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/RiskLevelValidationError.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/RiskLevelValidationError.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/RiskLevelValidationError.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/RiskLevelValidationError.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 import java.util.Objects;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationError.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationError.java
similarity index 67%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationError.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationError.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationError.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationError.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 /**
  * A validation error, found during the process of validating the Exposure Configuration. Can either
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationFailedException.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationFailedException.java
similarity index 78%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationFailedException.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationFailedException.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationFailedException.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationFailedException.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 /**
  * The validation could not be executed. Find more information about the root cause in the cause
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationResult.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationResult.java
similarity index 87%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationResult.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationResult.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ValidationResult.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ValidationResult.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 import java.util.HashSet;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/WeightValidationError.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/WeightValidationError.java
similarity index 94%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/WeightValidationError.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/WeightValidationError.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/exposureconfig/validation/WeightValidationError.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/WeightValidationError.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 import java.util.Objects;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/io/IO.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/io/IO.java
similarity index 96%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/io/IO.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/io/IO.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/io/IO.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/io/IO.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.io;
+package app.coronawarn.server.services.distribution.assembly.io;
 
 import java.io.File;
 import java.io.FileOutputStream;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/Writable.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/Writable.java
similarity index 74%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/Writable.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/Writable.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/Writable.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/Writable.java
@@ -1,7 +1,7 @@
-package app.coronawarn.server.services.distribution.structure;
+package app.coronawarn.server.services.distribution.assembly.structure;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 
 /**
  * Something that can be written to disk.
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/WritableImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/WritableImpl.java
similarity index 82%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/WritableImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/WritableImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/WritableImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/WritableImpl.java
@@ -1,6 +1,6 @@
-package app.coronawarn.server.services.distribution.structure;
+package app.coronawarn.server.services.distribution.assembly.structure;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
 import java.util.Objects;
 
 public abstract class WritableImpl implements Writable {
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/Directory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/Directory.java
similarity index 75%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/Directory.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/Directory.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/Directory.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/Directory.java
@@ -1,7 +1,7 @@
-package app.coronawarn.server.services.distribution.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.structure.directory;
 
-import app.coronawarn.server.services.distribution.structure.Writable;
-import app.coronawarn.server.services.distribution.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
 import java.util.Set;
 
 /**
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/DirectoryImpl.java
similarity index 87%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/DirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/DirectoryImpl.java
@@ -1,8 +1,8 @@
-package app.coronawarn.server.services.distribution.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.structure.directory;
 
-import app.coronawarn.server.services.distribution.structure.WritableImpl;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.WritableImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.HashSet;
 import java.util.Set;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectory.java
similarity index 77%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectory.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectory.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectory.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectory.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.structure.directory;
 
-import app.coronawarn.server.services.distribution.structure.functional.DirectoryFunction;
-import app.coronawarn.server.services.distribution.structure.functional.FileFunction;
-import app.coronawarn.server.services.distribution.structure.functional.Formatter;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.DirectoryFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.FileFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.Formatter;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.Set;
 import java.util.Stack;
 
@@ -32,7 +32,7 @@ public interface IndexDirectory<T> extends Directory {
   void addDirectoryToAll(DirectoryFunction directoryFunction);
 
   /**
-   * Calls the {@link app.coronawarn.server.services.distribution.structure.functional.IndexFunction}
+   * Calls the {@link app.coronawarn.server.services.distribution.assembly.structure.functional.IndexFunction}
    * with the {@code indices} to calculate and return the elements of the index of this {@link
    * IndexDirectory}.
    *
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectoryImpl.java
similarity index 85%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectoryImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectoryImpl.java
@@ -1,11 +1,11 @@
-package app.coronawarn.server.services.distribution.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.structure.directory;
 
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.functional.DirectoryFunction;
-import app.coronawarn.server.services.distribution.structure.functional.FileFunction;
-import app.coronawarn.server.services.distribution.structure.functional.Formatter;
-import app.coronawarn.server.services.distribution.structure.functional.IndexFunction;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.DirectoryFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.FileFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.Formatter;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.IndexFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Stack;
@@ -61,7 +61,7 @@ public void addDirectoryToAll(DirectoryFunction directoryFunction) {
    * Creates a new subdirectory for every element of the {@link IndexDirectory#getIndex index} and
    * writes {@link IndexDirectory#addFileToAll files} and {@link IndexDirectory#addDirectory
    * directories} to those. The respective element of the index will be pushed onto the Stack for
-   * subsequent {@link app.coronawarn.server.services.distribution.structure.Writable#prepare} calls
+   * subsequent {@link app.coronawarn.server.services.distribution.assembly.structure.Writable#prepare} calls
    * on those files and directories.
    *
    * @param indices A {@link Stack} of parameters from all {@link IndexDirectory IndexDirectories}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/DirectoryDecorator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorator/DirectoryDecorator.java
similarity index 75%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/DirectoryDecorator.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorator/DirectoryDecorator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/DirectoryDecorator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorator/DirectoryDecorator.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.structure.directory.decorator;
+package app.coronawarn.server.services.distribution.assembly.structure.directory.decorator;
 
-import app.coronawarn.server.services.distribution.structure.Writable;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.Set;
 
 /**
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/IndexingDecorator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorator/IndexingDecorator.java
similarity index 72%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/IndexingDecorator.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorator/IndexingDecorator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/directory/decorator/IndexingDecorator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorator/IndexingDecorator.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.structure.directory.decorator;
+package app.coronawarn.server.services.distribution.assembly.structure.directory.decorator;
 
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -14,7 +14,7 @@
 /**
  * A {@link DirectoryDecorator} that writes a file called {@code "index"}, containing a JSON array
  * containing all elements returned {@link IndexDirectoryImpl#getIndex}, formatted with the {@link
- * IndexDirectoryImpl#getIndexFormatter} on {@link app.coronawarn.server.services.distribution.structure.Writable#prepare}.
+ * IndexDirectoryImpl#getIndexFormatter} on {@link app.coronawarn.server.services.distribution.assembly.structure.Writable#prepare}.
  */
 public class IndexingDecorator<T> extends DirectoryDecorator {
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/File.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/File.java
similarity index 64%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/File.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/File.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/File.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/File.java
@@ -1,6 +1,6 @@
-package app.coronawarn.server.services.distribution.structure.file;
+package app.coronawarn.server.services.distribution.assembly.structure.file;
 
-import app.coronawarn.server.services.distribution.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
 
 /**
  * A {@link Writable} containing some bytes.
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/FileImpl.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileImpl.java
similarity index 69%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/FileImpl.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileImpl.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/FileImpl.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileImpl.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.structure.file;
+package app.coronawarn.server.services.distribution.assembly.structure.file;
 
-import app.coronawarn.server.services.distribution.io.IO;
-import app.coronawarn.server.services.distribution.structure.Writable;
-import app.coronawarn.server.services.distribution.structure.WritableImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.io.IO;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.WritableImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 
 /**
  * Implementation of {@link File} that interfaces with {@link java.io.File Files} on disk.
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/FileDecorator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorator/FileDecorator.java
similarity index 70%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/FileDecorator.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorator/FileDecorator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/FileDecorator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorator/FileDecorator.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.structure.file.decorator;
+package app.coronawarn.server.services.distribution.assembly.structure.file.decorator;
 
-import app.coronawarn.server.services.distribution.structure.Writable;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 
 /**
  * Decorates a {@link File} (e.g. to modify its content) on {@link Writable#prepare}. This class
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/SigningDecorator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorator/SigningDecorator.java
similarity index 85%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/SigningDecorator.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorator/SigningDecorator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/file/decorator/SigningDecorator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorator/SigningDecorator.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.structure.file.decorator;
+package app.coronawarn.server.services.distribution.assembly.structure.file.decorator;
 
 import app.coronawarn.server.common.protocols.internal.SignedPayload;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import com.google.protobuf.ByteString;
 import java.security.GeneralSecurityException;
 import java.security.PrivateKey;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/CheckedFunction.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/CheckedFunction.java
similarity index 85%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/CheckedFunction.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/CheckedFunction.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/CheckedFunction.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/CheckedFunction.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.structure.functional;
+package app.coronawarn.server.services.distribution.assembly.structure.functional;
 
 import java.util.function.Function;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/DirectoryFunction.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/DirectoryFunction.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/DirectoryFunction.java
@@ -0,0 +1,14 @@
+package app.coronawarn.server.services.distribution.assembly.structure.functional;
+
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
+import java.util.function.Function;
+
+/**
+ * A {@code Function<Stack<Object>, Directory>}.
+ */
+@FunctionalInterface
+public interface DirectoryFunction extends Function<ImmutableStack<Object>, Directory> {
+
+  Directory apply(ImmutableStack<Object> t);
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/FileFunction.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/FileFunction.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/FileFunction.java
@@ -0,0 +1,14 @@
+package app.coronawarn.server.services.distribution.assembly.structure.functional;
+
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
+import java.util.function.Function;
+
+/**
+ * A {@code Function<Stack<Object>, File>}.
+ */
+@FunctionalInterface
+public interface FileFunction extends Function<ImmutableStack<Object>, File> {
+
+  File apply(ImmutableStack<Object> t);
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/Formatter.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/Formatter.java
similarity index 74%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/Formatter.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/Formatter.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/Formatter.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/Formatter.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.structure.functional;
+package app.coronawarn.server.services.distribution.assembly.structure.functional;
 
 import java.util.function.Function;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/IndexFunction.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/IndexFunction.java
similarity index 64%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/IndexFunction.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/IndexFunction.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/IndexFunction.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/functional/IndexFunction.java
@@ -1,6 +1,6 @@
-package app.coronawarn.server.services.distribution.structure.functional;
+package app.coronawarn.server.services.distribution.assembly.structure.functional;
 
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.util.Set;
 import java.util.function.Function;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/util/ImmutableStack.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/util/ImmutableStack.java
similarity index 84%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/util/ImmutableStack.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/util/ImmutableStack.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/util/ImmutableStack.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/util/ImmutableStack.java
@@ -1,10 +1,10 @@
-package app.coronawarn.server.services.distribution.structure.util;
+package app.coronawarn.server.services.distribution.assembly.structure.util;
 
 import java.util.Stack;
 
 public class ImmutableStack<T> {
 
-  private Stack<T> stack;
+  private final Stack<T> stack;
 
   public ImmutableStack() {
     this.stack = new Stack<>();
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java
@@ -36,9 +36,9 @@
 @Component
 public class ObjectStoreAccess {
 
-  private Logger logger = LoggerFactory.getLogger(this.getClass());
+  private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  private String bucket;
+  private final String bucket;
 
   private S3Client client;
 
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3Publisher.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3Publisher.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3Publisher.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3Publisher.java
@@ -16,14 +16,14 @@
 @Component
 public class S3Publisher {
 
-  private Logger logger = LoggerFactory.getLogger(this.getClass());
+  private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
   /**
    * prefix path on S3, enforced for all methods on this class.
    */
   private String prefixPath = "cwa/";
 
-  private ObjectStoreAccess objectStoreAccess;
+  private final ObjectStoreAccess objectStoreAccess;
 
   @Autowired
   public S3Publisher(ObjectStoreAccess objectStoreAccess) {
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/Assembly.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/Assembly.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/Assembly.java
@@ -0,0 +1,42 @@
+package app.coronawarn.server.services.distribution.runner;
+
+import app.coronawarn.server.services.distribution.assembly.component.CwaApiStructureProvider;
+import app.coronawarn.server.services.distribution.assembly.component.OutputDirectoryProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
+import java.io.IOException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+/**
+ * This runner assembles and writes diagnosis key bundles and the parameter configuration.
+ */
+@Component
+@Order(2)
+public class Assembly implements ApplicationRunner {
+
+  private static final Logger logger = LoggerFactory.getLogger(Assembly.class);
+
+  @Autowired
+  private OutputDirectoryProvider outputDirectoryProvider;
+
+  @Autowired
+  private CwaApiStructureProvider cwaApiStructureProvider;
+
+  @Override
+  public void run(ApplicationArguments args) throws IOException {
+    Directory outputDirectory = this.outputDirectoryProvider.getDirectory();
+    outputDirectory.addDirectory(cwaApiStructureProvider.getDirectory());
+    this.outputDirectoryProvider.clear();
+    logger.debug("Preparing files...");
+    outputDirectory.prepare(new ImmutableStack<>());
+    logger.debug("Writing files...");
+    outputDirectory.write();
+    logger.debug("Distribution run finished successfully.");
+  }
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/DiagnosisKeyDistributionRunner.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/DiagnosisKeyDistributionRunner.java
deleted file mode 100644
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/DiagnosisKeyDistributionRunner.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package app.coronawarn.server.services.distribution.runner;
-
-import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.common.persistence.service.DiagnosisKeyService;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory.DiagnosisKeysDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Set;
-import org.apache.commons.io.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Component;
-
-@Component
-@Order(3)
-/**
- * This runner retrieves stored diagnosis keys, the generates and persists the respective diagnosis
- * key bundles.
- */
-public class DiagnosisKeyDistributionRunner implements ApplicationRunner {
-
-  private static final Logger logger = LoggerFactory
-      .getLogger(DiagnosisKeyDistributionRunner.class);
-
-  private static final String VERSION_DIRECTORY = "version";
-
-  @Value("${app.coronawarn.server.services.distribution.version}")
-  private String version;
-
-  @Value("${app.coronawarn.server.services.distribution.paths.output}")
-  private String outputPath;
-
-  @Autowired
-  private CryptoProvider cryptoProvider;
-
-  @Autowired
-  private DiagnosisKeyService diagnosisKeyService;
-
-
-  @Override
-  public void run(ApplicationArguments args) throws IOException {
-    Collection<DiagnosisKey> diagnosisKeys = diagnosisKeyService.getDiagnosisKeys();
-
-    DiagnosisKeysDirectoryImpl diagnosisKeysDirectory =
-        new DiagnosisKeysDirectoryImpl(diagnosisKeys, cryptoProvider);
-
-    IndexDirectory<?> versionDirectory =
-        new IndexDirectoryImpl<>(VERSION_DIRECTORY, __ -> Set.of(version), Object::toString);
-
-    versionDirectory.addDirectoryToAll(__ -> diagnosisKeysDirectory);
-
-    java.io.File outputDirectory = new File(outputPath);
-    clearDirectory(outputDirectory);
-    DirectoryImpl root = new DirectoryImpl(outputDirectory);
-    root.addDirectory(new IndexingDecorator<>(versionDirectory));
-
-    root.prepare(new ImmutableStack<>());
-    root.write();
-    logger.debug("Diagnosis key distribution structure written successfully.");
-  }
-
-  private static void clearDirectory(File directory) throws IOException {
-    FileUtils.deleteDirectory(directory);
-    directory.mkdirs();
-  }
-}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/ExposureConfigurationDistributionRunner.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/ExposureConfigurationDistributionRunner.java
deleted file mode 100644
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/ExposureConfigurationDistributionRunner.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package app.coronawarn.server.services.distribution.runner;
-
-import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.exposureconfig.ExposureConfigurationProvider;
-import app.coronawarn.server.services.distribution.exposureconfig.UnableToLoadFileException;
-import app.coronawarn.server.services.distribution.exposureconfig.structure.directory.ExposureConfigurationDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
-import java.io.File;
-import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Component;
-
-@Component
-@Order(2)
-/**
- * Reads the exposure configuration parameters from the respective file in the class path, then
- * generates and persists the respective exposure configuration bundle.
- */
-public class ExposureConfigurationDistributionRunner implements ApplicationRunner {
-
-  private static final Logger logger =
-      LoggerFactory.getLogger(ExposureConfigurationDistributionRunner.class);
-
-  @Value("${app.coronawarn.server.services.distribution.version}")
-  private String version;
-
-  @Value("${app.coronawarn.server.services.distribution.paths.output}")
-  private String outputPath;
-
-  @Autowired
-  private CryptoProvider cryptoProvider;
-
-  private static final String VERSION_DIRECTORY = "version";
-
-  @Override
-  public void run(ApplicationArguments args) {
-    var riskScoreParameters = readExposureConfiguration();
-    IndexDirectory<?> versionDirectory =
-        new IndexDirectoryImpl<>(VERSION_DIRECTORY, __ -> Set.of(version), Object::toString);
-    ExposureConfigurationDirectoryImpl parametersDirectory =
-        new ExposureConfigurationDirectoryImpl(riskScoreParameters, cryptoProvider);
-    Directory root = new DirectoryImpl(new File(outputPath));
-    versionDirectory.addDirectoryToAll(__ -> parametersDirectory);
-    root.addDirectory(new IndexingDecorator<>(versionDirectory));
-    root.prepare(new ImmutableStack<>());
-    root.write();
-    logger.debug("Exposure configuration structure written successfully.");
-  }
-
-  private RiskScoreParameters readExposureConfiguration() {
-    try {
-      return ExposureConfigurationProvider.readMasterFile();
-    } catch (UnableToLoadFileException e) {
-      logger.error("Could not load exposure configuration parameters", e);
-      throw new RuntimeException(e);
-    }
-  }
-}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunner.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java
similarity index 85%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunner.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunner.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java
@@ -16,15 +16,15 @@
  */
 @Component
 @Order(1)
-public class RetentionPolicyRunner implements ApplicationRunner {
+public class RetentionPolicy implements ApplicationRunner {
 
   private static final Logger logger = LoggerFactory
-      .getLogger(RetentionPolicyRunner.class);
+      .getLogger(RetentionPolicy.class);
 
   @Autowired
   private DiagnosisKeyService diagnosisKeyService;
 
-  @Value("${app.coronawarn.server.services.distribution.retention_days}")
+  @Value("${services.distribution.retention_days}")
   private Integer rententionDays;
 
   @Override
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3DistributionRunner.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3Distribution.java
similarity index 69%
rename from services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3DistributionRunner.java
rename to services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3Distribution.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3DistributionRunner.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/S3Distribution.java
@@ -1,12 +1,12 @@
 package app.coronawarn.server.services.distribution.runner;
 
+import app.coronawarn.server.services.distribution.assembly.component.OutputDirectoryProvider;
 import app.coronawarn.server.services.distribution.objectstore.S3Publisher;
 import java.io.IOException;
 import java.nio.file.Path;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
 import org.springframework.core.annotation.Order;
@@ -16,13 +16,13 @@
  * This runner will sync the base working directory to the S3.
  */
 @Component
-@Order(4)
-public class S3DistributionRunner implements ApplicationRunner {
+@Order(3)
+public class S3Distribution implements ApplicationRunner {
 
-  private Logger logger = LoggerFactory.getLogger(this.getClass());
+  private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  @Value("${app.coronawarn.server.services.distribution.paths.output}")
-  private String workdir;
+  @Autowired
+  private OutputDirectoryProvider outputDirectoryProvider;
 
   @Autowired
   private S3Publisher s3Publisher;
@@ -30,8 +30,7 @@ public class S3DistributionRunner implements ApplicationRunner {
   @Override
   public void run(ApplicationArguments args) {
     try {
-      Path pathToDistribute = Path.of(workdir).toAbsolutePath();
-
+      Path pathToDistribute = outputDirectoryProvider.getFileOnDisk().toPath().toAbsolutePath();
       s3Publisher.publishFolder(pathToDistribute);
     } catch (IOException | UnsupportedOperationException e) {
       logger.error("Distribution failed.", e);
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/DirectoryFunction.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/DirectoryFunction.java
deleted file mode 100644
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/DirectoryFunction.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package app.coronawarn.server.services.distribution.structure.functional;
-
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
-import java.util.function.Function;
-
-/**
- * A {@code Function<Stack<Object>, Directory>}.
- */
-@FunctionalInterface
-public interface DirectoryFunction extends Function<ImmutableStack<Object>, Directory> {
-
-  Directory apply(ImmutableStack<Object> t);
-}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/FileFunction.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/FileFunction.java
deleted file mode 100644
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/structure/functional/FileFunction.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package app.coronawarn.server.services.distribution.structure.functional;
-
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
-import java.util.function.Function;
-
-/**
- * A {@code Function<Stack<Object>, File>}.
- */
-@FunctionalInterface
-public interface FileFunction extends Function<ImmutableStack<Object>, File> {
-
-  File apply(ImmutableStack<Object> t);
-}
diff --git a/services/distribution/src/main/resources/application.properties b/services/distribution/src/main/resources/application.properties
--- a/services/distribution/src/main/resources/application.properties
+++ b/services/distribution/src/main/resources/application.properties
@@ -2,11 +2,10 @@ logging.level.org.springframework.web=INFO
 logging.level.app.coronawarn=INFO
 spring.main.web-application-type=NONE
 
-app.coronawarn.server.services.distribution.retention_days=14
-app.coronawarn.server.services.distribution.version=v1
-app.coronawarn.server.services.distribution.paths.output=out
-app.coronawarn.server.services.distribution.paths.privatekey=classpath:certificates/client/private.pem
-app.coronawarn.server.services.distribution.paths.certificate=classpath:certificates/chain/certificate.crt
+services.distribution.retention_days=14
+services.distribution.paths.output=out
+services.distribution.paths.privatekey=classpath:certificates/client/private.pem
+services.distribution.paths.certificate=classpath:certificates/chain/certificate.crt
 
 spring.flyway.enabled=false
 spring.jpa.hibernate.ddl-auto=validate

</patch>
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/ApplicationInitializationTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/ApplicationInitializationTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/ApplicationInitializationTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/ApplicationInitializationTest.java
@@ -3,7 +3,7 @@
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import app.coronawarn.server.common.persistence.service.DiagnosisKeyService;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
 import app.coronawarn.server.services.distribution.objectstore.S3Publisher;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/crypto/CryptoProviderTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java
similarity index 92%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/crypto/CryptoProviderTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/crypto/CryptoProviderTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.crypto;
+package app.coronawarn.server.services.distribution.assembly.component;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysStructureProviderDirectoryTest.java
similarity index 93%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysStructureProviderDirectoryTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysStructureProviderDirectoryTest.java
@@ -1,14 +1,14 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory;
 
 import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeyForSubmissionTimestamp;
 import static java.lang.String.join;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -31,7 +31,7 @@
 @ExtendWith(SpringExtension.class)
 @ContextConfiguration(classes = {CryptoProvider.class},
     initializers = ConfigFileApplicationContextInitializer.class)
-public class DiagnosisKeysDirectoryTest {
+public class DiagnosisKeysStructureProviderDirectoryTest {
 
   @Autowired
   CryptoProvider cryptoProvider;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTimeTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTimeTest.java
similarity index 97%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTimeTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTimeTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/diagnosiskeys/util/DateTimeTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTimeTest.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.diagnosiskeys.util;
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util;
 
 import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeyForDateTime;
 import static java.util.Collections.emptyList;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationMasterFileTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationStructureProviderMasterFileTest.java
similarity index 64%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationMasterFileTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationStructureProviderMasterFileTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationMasterFileTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationStructureProviderMasterFileTest.java
@@ -1,9 +1,9 @@
-package app.coronawarn.server.services.distribution.exposureconfig;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import app.coronawarn.server.services.distribution.exposureconfig.validation.ExposureConfigurationValidator;
-import app.coronawarn.server.services.distribution.exposureconfig.validation.ValidationResult;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.validation.ExposureConfigurationValidator;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.validation.ValidationResult;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -12,7 +12,7 @@
  *
  * There should never be any deployment when this test is failing.
  */
-public class ExposureConfigurationMasterFileTest {
+public class ExposureConfigurationStructureProviderMasterFileTest {
 
   private static final ValidationResult SUCCESS = new ValidationResult();
 
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProviderTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationStructureProviderProviderTest.java
similarity index 87%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProviderTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationStructureProviderProviderTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/ExposureConfigurationProviderTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/ExposureConfigurationStructureProviderProviderTest.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -6,7 +6,7 @@
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
 import org.junit.jupiter.api.Test;
 
-public class ExposureConfigurationProviderTest {
+public class ExposureConfigurationStructureProviderProviderTest {
 
   @Test
   public void okFile() throws UnableToLoadFileException {
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidatorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ExposureConfigurationStructureProviderValidatorTest.java
similarity index 87%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidatorTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ExposureConfigurationStructureProviderValidatorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/validation/ExposureConfigurationValidatorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/ExposureConfigurationStructureProviderValidatorTest.java
@@ -1,18 +1,18 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-import app.coronawarn.server.services.distribution.exposureconfig.ExposureConfigurationProvider;
-import app.coronawarn.server.services.distribution.exposureconfig.UnableToLoadFileException;
-import app.coronawarn.server.services.distribution.exposureconfig.validation.WeightValidationError.ErrorType;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.ExposureConfigurationProvider;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.UnableToLoadFileException;
+import app.coronawarn.server.services.distribution.assembly.exposureconfig.validation.WeightValidationError.ErrorType;
 import java.util.stream.Stream;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
-public class ExposureConfigurationValidatorTest {
+public class ExposureConfigurationStructureProviderValidatorTest {
 
   private static final ValidationResult SUCCESS = new ValidationResult();
 
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/validation/TestWithExpectedResult.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/TestWithExpectedResult.java
similarity index 83%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/validation/TestWithExpectedResult.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/TestWithExpectedResult.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/exposureconfig/validation/TestWithExpectedResult.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/exposureconfig/validation/TestWithExpectedResult.java
@@ -1,4 +1,4 @@
-package app.coronawarn.server.services.distribution.exposureconfig.validation;
+package app.coronawarn.server.services.distribution.assembly.exposureconfig.validation;
 
 public class TestWithExpectedResult {
 
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/WritableTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/WritableTest.java
similarity index 81%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/WritableTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/WritableTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/WritableTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/WritableTest.java
@@ -1,11 +1,11 @@
-package app.coronawarn.server.services.distribution.structure;
+package app.coronawarn.server.services.distribution.assembly.structure;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.io.File;
 import org.junit.jupiter.api.Test;
 
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/DirectoryTest.java
similarity index 90%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/DirectoryTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/DirectoryTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/DirectoryTest.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.structure.directory;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.io.IOException;
 import java.util.Set;
 import org.junit.Rule;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectoryTest.java
similarity index 85%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectoryTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/IndexDirectoryTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/IndexDirectoryTest.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.structure.directory;
+package app.coronawarn.server.services.distribution.assembly.structure.directory;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import app.coronawarn.server.services.distribution.structure.Writable;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.functional.Formatter;
-import app.coronawarn.server.services.distribution.structure.functional.IndexFunction;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.Writable;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.Formatter;
+import app.coronawarn.server.services.distribution.assembly.structure.functional.IndexFunction;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/decorators/DirectoryDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorators/DirectoryDecoratorTest.java
similarity index 63%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/decorators/DirectoryDecoratorTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorators/DirectoryDecoratorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/decorators/DirectoryDecoratorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorators/DirectoryDecoratorTest.java
@@ -1,14 +1,14 @@
-package app.coronawarn.server.services.distribution.structure.directory.decorators;
+package app.coronawarn.server.services.distribution.assembly.structure.directory.decorators;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.DirectoryDecorator;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.DirectoryDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import org.junit.jupiter.api.Test;
 
 public class DirectoryDecoratorTest {
@@ -52,7 +52,7 @@ public void checkProxiesAllMethods() {
     verify(decoree).getFileOnDisk();
   }
 
-  private class TestDirectoryDecorator extends DirectoryDecorator {
+  private static class TestDirectoryDecorator extends DirectoryDecorator {
 
     protected TestDirectoryDecorator(Directory directory) {
       super(directory);
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/decorators/IndexingDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorators/IndexingDecoratorTest.java
similarity index 77%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/decorators/IndexingDecoratorTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorators/IndexingDecoratorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/directory/decorators/IndexingDecoratorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/directory/decorators/IndexingDecoratorTest.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.structure.directory.decorators;
+package app.coronawarn.server.services.distribution.assembly.structure.directory.decorators;
 
 import static app.coronawarn.server.services.distribution.common.Helpers.prepareAndWrite;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.structure.directory.IndexDirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.directory.decorator.IndexingDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexingDecorator;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/FileTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileTest.java
similarity index 81%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/FileTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/FileTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileTest.java
@@ -1,11 +1,11 @@
-package app.coronawarn.server.services.distribution.structure.file;
+package app.coronawarn.server.services.distribution.assembly.structure.file;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
 import java.io.IOException;
 import java.nio.file.Files;
 import org.junit.Rule;
@@ -15,7 +15,7 @@
 
 public class FileTest {
 
-  private byte[] bytes = "World".getBytes();
+  private final byte[] bytes = "World".getBytes();
   private File file;
 
   @Rule
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/decorators/FileDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorators/FileDecoratorTest.java
similarity index 62%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/decorators/FileDecoratorTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorators/FileDecoratorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/decorators/FileDecoratorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorators/FileDecoratorTest.java
@@ -1,13 +1,13 @@
-package app.coronawarn.server.services.distribution.structure.file.decorators;
+package app.coronawarn.server.services.distribution.assembly.structure.file.decorators;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.decorator.FileDecorator;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.decorator.FileDecorator;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import org.junit.jupiter.api.Test;
 
 public class FileDecoratorTest {
@@ -45,7 +45,7 @@ public void checkProxiesAllMethods() {
     verify(decoree).getFileOnDisk();
   }
 
-  private class TestFileDecorator extends FileDecorator {
+  private static class TestFileDecorator extends FileDecorator {
 
     protected TestFileDecorator(File file) {
       super(file);
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/decorators/SigningDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorators/SigningDecoratorTest.java
similarity index 84%
rename from services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/decorators/SigningDecoratorTest.java
rename to services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorators/SigningDecoratorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/structure/file/decorators/SigningDecoratorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/file/decorators/SigningDecoratorTest.java
@@ -1,16 +1,16 @@
-package app.coronawarn.server.services.distribution.structure.file.decorators;
+package app.coronawarn.server.services.distribution.assembly.structure.file.decorators;
 
 import static app.coronawarn.server.services.distribution.common.Helpers.prepareAndWrite;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import app.coronawarn.server.common.protocols.internal.SignedPayload;
-import app.coronawarn.server.services.distribution.crypto.CryptoProvider;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.directory.DirectoryImpl;
-import app.coronawarn.server.services.distribution.structure.file.File;
-import app.coronawarn.server.services.distribution.structure.file.FileImpl;
-import app.coronawarn.server.services.distribution.structure.file.decorator.SigningDecorator;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.File;
+import app.coronawarn.server.services.distribution.assembly.structure.file.FileImpl;
+import app.coronawarn.server.services.distribution.assembly.structure.file.decorator.SigningDecorator;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java
@@ -1,8 +1,8 @@
 package app.coronawarn.server.services.distribution.common;
 
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessTest.java
@@ -20,11 +20,11 @@
 public class ObjectStoreAccessTest {
 
 
-  private Logger logger = LoggerFactory.getLogger(this.getClass());
+  private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  private String testRunId = "testing/cwa/" + UUID.randomUUID().toString() + "/";
+  private final String testRunId = "testing/cwa/" + UUID.randomUUID().toString() + "/";
 
-  private String textFile = "objectstore/store-test-file";
+  private final String textFile = "objectstore/store-test-file";
 
   @Autowired
   private ObjectStoreAccess objectStoreAccess;
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3PublisherTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3PublisherTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3PublisherTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3PublisherTest.java
@@ -23,11 +23,11 @@
 @TestInstance(Lifecycle.PER_CLASS)
 public class S3PublisherTest {
 
-  private String testRunId = "testing/cwa/" + UUID.randomUUID().toString() + "/";
+  private final String testRunId = "testing/cwa/" + UUID.randomUUID().toString() + "/";
 
-  private String rootTestFolder = "objectstore/publisher/";
+  private final String rootTestFolder = "objectstore/publisher/";
 
-  private String exampleFile = rootTestFolder + "rootfile";
+  private final String exampleFile = rootTestFolder + "rootfile";
 
   @Autowired
   private S3Publisher s3Publisher;
diff --git a/services/distribution/src/test/resources/application.properties b/services/distribution/src/test/resources/application.properties
--- a/services/distribution/src/test/resources/application.properties
+++ b/services/distribution/src/test/resources/application.properties
@@ -3,10 +3,9 @@ logging.level.org.springframework=off
 logging.level.root=off
 spring.main.banner-mode=off
 
-app.coronawarn.server.services.distribution.retention_days=14
-app.coronawarn.server.services.distribution.version=v1
-app.coronawarn.server.services.distribution.paths.output=out
-app.coronawarn.server.services.distribution.paths.privatekey=classpath:certificates/client/private.pem
-app.coronawarn.server.services.distribution.paths.certificate=classpath:certificates/chain/certificate.crt
+services.distribution.retention_days=14
+services.distribution.paths.output=out
+services.distribution.paths.privatekey=classpath:certificates/client/private.pem
+services.distribution.paths.certificate=classpath:certificates/chain/certificate.crt
 
 spring.flyway.enabled=false
