|
1 | | -# Spring Boot Starter (template) |
2 | | - |
3 | | -A minimal working starter template for a Spring Boot non-web applications using the |
4 | | -Spring [CommandLineRunner](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/CommandLineRunner.html) |
5 | | -interface with a demonstration of Java |
6 | | -annotation-based [Inversion of Control](https://stackoverflow.com/questions/3058/what-is-inversion-of-control) |
7 | | -via [Dependency Injection](https://stackoverflow.com/questions/130794/what-is-dependency-injection). |
8 | | - |
9 | | -## Run the app |
10 | | - |
11 | | -Checkout the latest code from `main` and run the Maven goal `spring-boot:run`: |
12 | | - |
13 | | -``` |
14 | | -mvn spring-boot:run |
15 | | -``` |
16 | | - |
17 | | -## What the app does |
18 | | - |
19 | | -This small app just parses a file with a collection of good coding prayers and creates a singleton |
20 | | -instance of an `CodingPrayersMessageService`. This concrete implementation uses the |
21 | | -interface `MessageService`, that comes with only one public method: `String collectMessage()`. |
22 | | - |
23 | | -This service is used to demonstrate the IoC principle. We have defined another interface `NewsMedia` |
24 | | -and provide a concrete implementation `DeveloperNews`, that will call the message service to receive |
25 | | -recent news and forward them to the caller. |
26 | | - |
27 | | -In the main app code, we just retrieve this Singleton instance or Bean in Spring lingua from the |
28 | | -loaded context and call the news media `getNEws()` method. The collected message is then printed out |
29 | | -to the command line interface: |
30 | | - |
31 | | -``` |
32 | | -
|
33 | | - . ____ _ __ _ _ |
34 | | - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ |
35 | | -( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ |
36 | | - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) |
37 | | - ' |____| .__|_| |_|_| |_\__, | / / / / |
38 | | - =========|_|==============|___/=/_/_/_/ |
39 | | - :: Spring Boot :: (v2.5.6) |
40 | | -
|
41 | | -2021-11-18 09:17:37.640 INFO 68052 --- [ main] l.q.s.SpringMinimalTemplateApplication : Starting SpringMinimalTemplateApplication using Java 17.0.1 on imperator.am10.uni-tuebingen.de with PID 68052 (/Users/sven1103/git/spring-boot-starter-template/target/classes started by sven1103 in /Users/sven1103/git/spring-boot-starter-template) |
42 | | -2021-11-18 09:17:37.641 INFO 68052 --- [ main] l.q.s.SpringMinimalTemplateApplication : No active profile set, falling back to default profiles: default |
43 | | -2021-11-18 09:17:38.164 INFO 68052 --- [ main] l.q.s.SpringMinimalTemplateApplication : Started SpringMinimalTemplateApplication in 0.808 seconds (JVM running for 1.489) |
44 | | -####################### Message of the day ################## |
45 | | -Have you written unit tests yet? If not, do it! |
46 | | -############################################################## |
47 | | -
|
| 1 | +# OpenBiS Dropboxes |
| 2 | + |
| 3 | +!!! abstract |
| 4 | +After the [Data Scanner](#), data registration in openbis can be automated by dropboxes. |
| 5 | +We use Java Dropboxes to move files into OpenBis (see |
| 6 | +the [Documentation on Java Dropboxes](https://openbis.readthedocs.io/en/20.10.x/software-developer-documentation/server-side-extensions/dss-dropboxes.html#java-dropboxes)) |
| 7 | + |
| 8 | +## Context |
| 9 | + |
| 10 | +```mermaid |
| 11 | +sequenceDiagram |
| 12 | + autonumber |
| 13 | + User ->> SFTP Folder : move files |
| 14 | + Data Scanner ->> SFTP Folder : is new data present? |
| 15 | + SFTP Folder ->> Data Scanner : Yes |
| 16 | + Data Scanner ->> Dropbox Incoming : move files |
| 17 | + Data Scanner ->> Dropbox Incoming : create marker file |
| 18 | + Dropbox ->> Dropbox Incoming : Is marker file present? |
| 19 | + Dropbox Incoming ->> Dropbox : Yes |
| 20 | + Dropbox ->> OpenBiS : Register data |
48 | 21 | ``` |
49 | 22 |
|
50 | | -## Realisation of IoC and DPI |
51 | | - |
52 | | -The messages collection is stored in a simple text file `messages.txt`, that is provided with the |
53 | | -apps `resources`. Just go ahead and change the content of the file and run the app! |
54 | | - |
55 | | -<img width="308" alt="grafik" src="https://user-images.githubusercontent.com/9976560/142380084-d01081d2-79fb-4ff3-acc5-3140dca38f6a.png"> |
56 | | - |
57 | | - |
58 | | -So how does the app know where to find this file and **load the messages content**? |
59 | | - |
60 | | -We have configured it as a **external property** in a file `application.properties` and load the |
61 | | -configuration on application startup! Cool eh? |
62 | | - |
63 | | -<img width="381" alt="grafik" src="https://user-images.githubusercontent.com/9976560/142376871-5bee068f-208c-4af0-a35b-9442ee498789.png"> |
64 | 23 |
|
65 | | -This is how the file content looks like: |
| 24 | +### Dropbox Process |
66 | 25 |
|
67 | | -``` |
68 | | -messages.file=messages.txt |
69 | | -``` |
70 | | - |
71 | | -So how do we access the value of the `messages.file` property in our application with Spring? |
72 | | - |
73 | | -Have a look in the class `AppConfig`, there the magic happens: |
| 26 | +The dropbox processes the data before moving it into OpenBiS. A successful processing is shown |
| 27 | +below. |
74 | 28 |
|
75 | | -```groovy |
76 | | -@Configuration |
77 | | -@PropertySource("application.properties") |
78 | | -class AppConfig { |
| 29 | +The process is triggered when a marker filed called `MARKER_is_finished_<dirname>` with `<dirname>` being the folder to be registered, is created. |
79 | 30 |
|
80 | | - @Value('${messages.file}') |
81 | | - public String messagesFile |
| 31 | +``` mermaid |
| 32 | +sequenceDiagram |
| 33 | + autonumber |
| 34 | + IncomingFolder ->> Dropbox: Folder and Markerfile exist |
| 35 | + Dropbox ->> IncomingFolder: Read provenance.json |
| 36 | + Dropbox ->> Dropbox: Parse measurement ID |
| 37 | + Dropbox ->> OpenBiS DSS: Get measurement sample |
| 38 | + OpenBiS DSS ->> Dropbox: Give measurement sample |
| 39 | + Dropbox ->> OpenBiS DSS: Has measurement datasets? |
| 40 | + OpenBiS DSS ->> Dropbox: No |
| 41 | + Dropbox ->> Dropbox: Parse measurement properties |
| 42 | + Dropbox ->> OpenBiS DSS: Create dataset |
| 43 | + Dropbox ->> OpenBiS DSS: Send files to dataset |
82 | 44 | ``` |
83 | 45 |
|
84 | | -We define the property source, which is the file `application.properties` that is provided in the |
85 | | -resource folder of the app and available to the classpath. We also tell with the |
86 | | -annotation `@Configuration` Spring, hey this is a class that holds app configuration data! |
| 46 | +### Dropbox Configuration |
87 | 47 |
|
88 | | -With the annotation `@Value('${messages.file}')` we tell Spring, which property's value should be |
89 | | -injected. Here we make use of field injection, other types of injection like method and constructor |
90 | | -injection are also possible. |
| 48 | +The dropbox configuration can be found |
| 49 | +at `...` on the OpenBiS instance. Each folder within this directory creates a dropbox. |
91 | 50 |
|
92 | | -So how is the concrete implementation of the `MessageService` presented to Spring? We can use |
93 | | -the `@Bean` annotation here, to tell Spring: _hey, this is sth you must load on startup and provide |
94 | | -to the context_. |
| 51 | +Within each folder exists a directory `lib` and a file `plugin.properties`. The `lib` folder is |
| 52 | +where you place your dropbox `jar` file. The `plugin.properties` is where you configure your |
| 53 | +dropbox. |
95 | 54 |
|
96 | | -```java |
97 | | -@Configuration |
98 | | -@PropertySource("application.properties") |
99 | | -class AppConfig { |
100 | | - |
101 | | - .... |
102 | | - |
103 | | - @Bean |
104 | | - MessageService messageService() { |
105 | | - return new CodingPrayersMessageService(messagesFile) |
106 | | - } |
| 55 | +```properties title="plugin.properties" linenums="1" |
| 56 | +incoming-dir=... |
| 57 | +incoming-data-completeness-condition=marker-file |
| 58 | +top-level-data-set-handler=ch.systemsx.cisd.etlserver.registrator.api.v2.JavaTopLevelDataSetHandlerV2 |
| 59 | +program-class=life.qbic.registration.openbis.OpenBisDropboxETL |
| 60 | +storage-processor=ch.systemsx.cisd.etlserver.DefaultStorageProcessor |
107 | 61 | ``` |
108 | | - |
109 | | -That is all there is, you can now load the bean in your main application code: |
110 | | - |
111 | | -```java |
112 | | -@SpringBootApplication |
113 | | -class SpringMinimalTemplateApplication { |
114 | | - |
115 | | - static void main(String[] args) { |
116 | | - SpringApplication.run(SpringMinimalTemplateApplication, args) |
117 | | - // load the annotation context |
118 | | - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class) |
119 | | - // get the service bean |
120 | | - MessageService service = context.getBean("messageService", MessageService.class) |
121 | | - // collect the message and praise the magic |
122 | | - println service.collectMessage() |
123 | | - |
124 | | -``` |
125 | | - |
126 | | -### Inversion of Control |
127 | | - |
128 | | -<img width="990" alt="grafik" src="https://user-images.githubusercontent.com/9976560/144576610-4dd4aa0a-1b58-4832-aaac-0bb70757a9a1.png"> |
129 | | - |
130 | | -You might have already spotted the interface `NewsMedia` and its implementing class `DeveloperNews` |
131 | | -in the app's source code. Here you can see an example for the magic of inversion of control. |
132 | | -
|
133 | | -The `NewsMedia` interface is just an abstraction that we will later use, because we don't care about |
134 | | -the actual implementation details. By this, we also do not create any dependencies to concrete |
135 | | -implementation details but on actual behaviour. Concrete implementations can then later be exchanged |
136 | | -without causing any breaking changes in the client code base. |
137 | | - |
138 | | -The interface has only one method: `String getNews()`. Now let's have a closer look into the |
139 | | -class `DeveloperNews` that implements this interface: |
140 | | - |
141 | | -```java |
142 | | -class DeveloperNews implements NewsMedia{ |
143 | | - |
144 | | - private MessageService service |
145 | | - |
146 | | - DeveloperNews(MessageService service) { |
147 | | - this.service = service |
148 | | - } |
149 | | - |
150 | | - @Override |
151 | | - String getNews() { |
152 | | - return service.collectMessage() |
153 | | - } |
154 | | -} |
155 | | -``` |
156 | | - |
157 | | -When you check the constructor signature, you see that this method has only one argument, which is a |
158 | | -reference to an object of type `MessageService`. And when the `getNews()` method is called by the |
159 | | -client, the class delegates this request to the message service. Since we have stored the reference |
160 | | -in a private field, that is super easy, we known how to call the service. |
161 | | - |
162 | | -So why is this inversion of control? |
163 | | - |
164 | | -Because the `DeveloperNews` class does not manage the instantiation of a concrete message service. |
165 | | -The configuration happened outside of the class, therefore the DeveloperNews class has no direct |
166 | | -control over the instantiation. If it had, it would look like this: |
167 | | - |
168 | | -```java |
169 | | -DeveloperNews(String filePathToMessages) { |
170 | | - this.service = new CodingPrayersMessageService(filePathToMessages) |
171 | | -} |
172 | | -``` |
173 | | - |
174 | | -That doesn't look good, does it? In order to create an instance of a message service, we would need |
175 | | -to know the conrete implementation and its required properties (here it is the file path to |
176 | | -the `messages.txt`). So the `DeveloperNews` class has the control over the message service. |
177 | | -
|
178 | | -Instead, we would like to not take care about these details, so we invert the control and inject the |
179 | | -dependency via the constructor. |
180 | | -
|
181 | | -Please find more in depth documentation on the |
182 | | -official [Spring website](https://spring.io/projects/spring-framework). |
183 | | -
|
184 | | -
|
185 | | -
|
186 | | -
|
187 | | -
|
| 62 | +!!! info |
| 63 | +Ususally you only need to adapt the `incoming-dir` and `program-class` when setting up a new dropbox. |
| 64 | +For more configuration options please see the [OpenBiS documentation](https://openbis.readthedocs.io/en/20.10.x/software-developer-documentation/server-side-extensions/dss-dropboxes.html#configuration). |
0 commit comments