@asyncapi/generator 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/Dockerfile +7 -8
  3. package/docs/asyncapi-document.md +2 -1
  4. package/docs/configuration-file.md +112 -33
  5. package/docs/generator-template-java.md +560 -0
  6. package/docs/generator-template.md +62 -29
  7. package/docs/hooks.md +3 -3
  8. package/docs/installation-guide.md +5 -51
  9. package/docs/migration-cli.md +5 -5
  10. package/docs/migration-nunjucks-react.md +1 -1
  11. package/docs/nunjucks-render-engine.md +4 -2
  12. package/docs/template.md +2 -2
  13. package/docs/usage.md +14 -32
  14. package/docs/versioning.md +3 -1
  15. package/lib/conditionalGeneration.js +162 -0
  16. package/lib/filtersRegistry.js +1 -1
  17. package/lib/generator.js +93 -40
  18. package/lib/hooksRegistry.js +8 -1
  19. package/lib/logMessages.js +6 -1
  20. package/lib/parser.js +10 -0
  21. package/lib/templateConfigValidator.js +30 -1
  22. package/package.json +2 -3
  23. package/test/generator.test.js +1 -1
  24. package/test/hooksRegistry.test.js +173 -0
  25. package/test/integration.test.js +47 -0
  26. package/test/parser.test.js +100 -2
  27. package/test/templateConfigValidator.test.js +18 -1
  28. package/test/test-project/README.md +5 -1
  29. package/test/test-project/docker-compose.yml +7 -15
  30. package/test/test-project/test-project.test.js +6 -2
  31. package/test/test-project/test-registry.test.js +7 -2
  32. package/test/test-project/test.sh +1 -1
  33. package/test/test-templates/nunjucks-template/package-lock.json +43 -119
  34. package/test/test-templates/react-template/.ageneratorrc +33 -0
  35. package/test/test-templates/react-template/package.json +4 -20
  36. package/test/test-templates/react-template/template/conditionalFile.txt +0 -0
  37. package/test/test-templates/react-template/template/conditionalFolder/conditionalFile.txt +0 -0
  38. package/test/test-templates/react-template/template/conditionalFolder2/input.txt +0 -0
  39. package/test/utils.test.js +53 -0
  40. package/test/test-project/test-entrypoint.sh +0 -12
@@ -0,0 +1,560 @@
1
+ ---
2
+ title: "Creating a template - Java"
3
+ weight: 190
4
+ ---
5
+
6
+ This tutorial teaches you how to create a simple generator template using a Java MQTT client. You'll use the AsyncAPI document and the template you develop to generate Java code. Additionally, you'll create template code with a reusable component to reuse the custom functionality you create and test your code using an MQTT client.
7
+
8
+ This section guides you through creating a flexible MQTT-supported template that will generate a **Java** client from the template and the AsyncAPI document referenced above. The following steps are similar to the [Creating a template - Python](https://www.asyncapi.com/docs/tools/generator/generator-template) but with a few differences and Java flair. This will help developers practice with AsyncAPI generator tool using statically typed language **Java**.
9
+
10
+ ## Prerequisites
11
+
12
+ To run it, ensure you have Java JDK 8 or higher, Gradle, and the AsyncAPI generator.
13
+ - **Gradle** - Get gradle at https://gradle.org/install/
14
+ - **JDK** - Get jdk at https://www.oracle.com/ca-en/java/technologies/downloads/
15
+
16
+ ## Overview of Java Template
17
+
18
+ In this section, you'll:
19
+
20
+ 1. Create a new directory to run Java code.
21
+ 2. Create the Java client.
22
+ 3. Test the Java Client
23
+ 4. Output Java template code.
24
+ 5. Create more channels
25
+
26
+ ### 1. Create a new directory to run Java code
27
+
28
+ Create a new directory called **java-mqtt-client-template** at the root of your project. This is where all your Java templating work will go.
29
+
30
+ Once that is done, you should create some new sub-directories to begin building your Java client.
31
+ 1. Create a new subdirectory called `src`
32
+ 2. Change into `src` and create two new subdirectories: `fixtures` and `main/java`.
33
+ 3. Create a file named `asyncapi.yml` in your fixtures directory and paste the `asyncapi.yml` document mentioned [here](https://www.asyncapi.com/docs/tools/generator/generator-template#background-context) into it.
34
+ 4. Create a new file named **package.json** in your **java-mqtt-client-template** directory. This file is used to define the **dependencies** for your template.
35
+ 5. Create a new file called **build.gradle** in your **java-mqtt-client-template** directory. This file is used to build your generated java code for your template.
36
+ 6. Create a new file named **index.js** in a `template` folder from root directory. This file is used to define the **logic** for your template.
37
+
38
+ Now your directory should look like this:
39
+
40
+ ```
41
+ java-mqtt-client-template
42
+ ├── src
43
+ | └── fixtures
44
+ | └── asyncapi.yml
45
+ │ └── main/java
46
+ ├── template
47
+ | └── index.js
48
+ └── package.json
49
+ └── build.gradle
50
+ ```
51
+
52
+
53
+ Note: The `client.java` code must be in your `src/main/java` directory, or else Gradle won't build your application.
54
+
55
+ ### Java - package.json file
56
+ Add the following code snippet to your package.json file:
57
+
58
+ ```json
59
+ {
60
+ "name": "java-mqtt-client-template",
61
+ "version": "0.0.1",
62
+ "description": "A template that generates a Java MQTT client using MQTT.",
63
+ "generator": {
64
+ "renderer": "react",
65
+ "apiVersion": "v1",
66
+ "generator": ">=1.10.0 <2.0.0",
67
+ "supportedProtocols": ["mqtt"]
68
+ },
69
+ "dependencies": {
70
+ "@asyncapi/generator-react-sdk": "^0.2.25"
71
+ },
72
+ "devDependencies": {
73
+ "rimraf": "^5.0.0"
74
+ }
75
+ }
76
+ ```
77
+
78
+ Navigate to the `java-mqtt-client-template` directory and run the command `npm install` on your terminal to install the dependencies specified in `package.json`.
79
+
80
+ ### Java - index.js file
81
+
82
+ The **index.js** file is used to define the logic for your template. Inside the template folder, create an **index.js** file and add the code snippet below:
83
+
84
+ ```js
85
+ //1
86
+ import { File } from '@asyncapi/generator-react-sdk'
87
+ //2
88
+ export default function ({ asyncapi }) {
89
+ //3
90
+ return <File name="Client.java">{asyncapi.info().title()}</File>
91
+ }
92
+ ```
93
+
94
+ To see this in action, navigate to the `java-mqtt-client-template` directory. Then, run `asyncapi generate fromTemplate src/fixtures/asyncapi.yml ./ --output src/main/java` command in your terminal. You should get a sucess message as shown below and a `Client.java` file in `src/main/java`.
95
+
96
+ ``` cmd
97
+ Generation in progress. Keep calm and wait a bit... done
98
+ Check out your shiny new generated files at test/project.
99
+ ```
100
+
101
+ ### 2. Create the Java client
102
+
103
+ #### 2a. Setting up Gradle
104
+ The first step in creating the Java client to send messages using the MQTT protocol is to ensure that your `build.gradle` file includes the correct dependencies. Add the code snippet below into your `build.gradle` file.
105
+
106
+ ```groovy
107
+ plugins {
108
+ id 'java'
109
+ id 'application'
110
+ }
111
+
112
+ repositories {
113
+ mavenCentral()
114
+ }
115
+
116
+ dependencies {
117
+ implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
118
+ }
119
+
120
+ application{
121
+ mainClass = project.hasProperty('mainClass') ? project.mainClass : 'Client' // Default to 'Client' if no property is passed
122
+ }
123
+ ```
124
+
125
+ Here's what is contained in the code snippet above:
126
+
127
+ - **plugins** - This section defines the plugins applied to the Gradle project.
128
+ - **id 'java'** - This applies the Java plugin, which allows you to compile and run Java code.
129
+ - **id 'application'** - This plugin is used to support building and running applications. It helps with creating an executable JAR.
130
+ - **repositories** - This section tells you app to fetch dependencies from Maven Central to retrieve the MQTT client library.
131
+ - **dependencies** - This specifies that the project depends on the Eclipse Paho MQTT client version 1.2.5, which is needed to compile and run.
132
+ - **application** - This section defines how the application should be executed. **mainClass** specifies the main class to be executed in a Java application. It can be specified via the command line else it defaults to the **Client.java** file.
133
+
134
+ Navigate to the `java-mqtt-client-template` directory. Run the command `gradle build` in your terminal to build your Java application. **Note**: Every time you update the `build.gradle` file, you must recompile it to get the new changes.
135
+
136
+ #### 2b. Beefing up Client.java
137
+
138
+ Here is the sample code to pasted into the `Client.java` file you generated above running the `asyncapi generate fromTemplate src/fixtures/asyncapi.yml ./ --output src/main/java` command.
139
+
140
+ ```java
141
+ import org.eclipse.paho.client.mqttv3.*;
142
+
143
+ public class Client {
144
+ private static final String BROKER_URL = "tcp://test.mosquitto.org:1883";
145
+ private static final String TOPIC = "temperature/changed";
146
+
147
+ private MqttClient client;
148
+
149
+ public Client() {
150
+ try {
151
+ // Generate a unique client ID
152
+ String clientId = MqttClient.generateClientId();
153
+
154
+ // Create and connect the MQTT client
155
+ client = new MqttClient(BROKER_URL, clientId);
156
+ MqttConnectOptions options = new MqttConnectOptions();
157
+ options.setCleanSession(true);
158
+
159
+ client.connect(options);
160
+ System.out.println("Connected to MQTT broker: " + BROKER_URL);
161
+ } catch (MqttException e) {
162
+ e.printStackTrace();
163
+ }
164
+ }
165
+ public void sendTemperatureChange(String id) {
166
+ try {
167
+ // Publish the message with the temperature change
168
+ MqttMessage message = new MqttMessage(id.getBytes());
169
+ client.publish(TOPIC, message);
170
+ } catch (MqttException e) {
171
+ e.printStackTrace();
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### 3. Test the Java Client
178
+ Create a **src/main/java/TestClient.java** file in your project and add the code snippet below.
179
+
180
+ Your directory should now look like this:
181
+
182
+ ```
183
+ java-mqtt-client-template
184
+ ├── src
185
+ | └── fixtures
186
+ | └── asyncapi.yml
187
+ │ └── main/java
188
+ | └── Client.java
189
+ | └── TestClient.java
190
+ ├── template
191
+ | └── index.js
192
+ └── package.json
193
+ └── build.gradle
194
+ ```
195
+
196
+ ```java
197
+ import java.util.Random;
198
+ import java.util.concurrent.TimeUnit;
199
+
200
+ public class TestClient {
201
+ public static void main(String[] args) {
202
+ Client client = new Client();
203
+ Random random = new Random();
204
+
205
+ int idLength = 8;
206
+ int minValue = (int) Math.pow(10, idLength - 1); // Minimum 8-digit number (e.g., 10000000)
207
+ int maxValue = (int) Math.pow(10, idLength) - 1; // Maximum 8-digit number (e.g., 99999999)
208
+ System.out.println("Validating generated generated Client.java");
209
+ System.out.println("Running tests in TestClient.java");
210
+ System.out.println("Sending temperature changes to the broker...");
211
+ System.err.println("\n");
212
+ while (true) {
213
+ int randomId = random.nextInt(maxValue - minValue + 1) + minValue;
214
+ client.sendTemperatureChange(String.valueOf(randomId));
215
+ System.out.println("New temperature detected " + randomId + " sent to temperature/changed");
216
+
217
+ try {
218
+ TimeUnit.SECONDS.sleep(1); // Sleep for 1 second
219
+ } catch (InterruptedException e) {
220
+ e.printStackTrace();
221
+ }
222
+ }
223
+ }
224
+ }
225
+ ```
226
+ Run the command `gradle run -PmainClass=TestClient` to run your Java program with Gradle on your terminal. You should see output similar to the snippet below logged on your terminal:
227
+
228
+ ``` cmd
229
+ New temperature detected 64250266 sent to temperature/changed
230
+ New temperature detected 36947728 sent to temperature/changed
231
+ New temperature detected 72955029 sent to temperature/changed
232
+ ```
233
+ ### 4. Output Java template code.
234
+ Open **index.js** and copy the content below so your file looks like the code snippet below:
235
+
236
+ ```js
237
+ //1
238
+ import { File } from '@asyncapi/generator-react-sdk'
239
+ //2
240
+ export default function ({ asyncapi }) {
241
+ //3
242
+ return <File name="Client.java">
243
+ {`
244
+
245
+ import org.eclipse.paho.client.mqttv3.*;
246
+ public class Client {
247
+ private static final String BROKER_URL = "tcp://test.mosquitto.org:1883";
248
+ private static final String TOPIC = "temperature/changed";
249
+
250
+ private MqttClient client;
251
+
252
+ public Client() {
253
+ try {
254
+ // Generate a unique client ID
255
+ String clientId = MqttClient.generateClientId();
256
+
257
+ // Create and connect the MQTT client
258
+ client = new MqttClient(BROKER_URL, clientId);
259
+ MqttConnectOptions options = new MqttConnectOptions();
260
+ options.setCleanSession(true);
261
+
262
+ client.connect(options);
263
+ System.out.println("Connected to MQTT broker: " + BROKER_URL);
264
+ } catch (MqttException e) {
265
+ e.printStackTrace();
266
+ }
267
+ }
268
+ public void sendTemperatureChange(String id) {
269
+ try {
270
+ // Publish the message with the temperature change
271
+ MqttMessage message = new MqttMessage(id.getBytes());
272
+ client.publish(TOPIC, message);
273
+ } catch (MqttException e) {
274
+ e.printStackTrace();
275
+ }
276
+ }
277
+ }`
278
+ }</File>
279
+ }
280
+ ```
281
+ #### 4a. Write the script to run the test code
282
+ In **package.json** define a script property that you invoke by calling `npm run <your_script>`. After adding these scripts in **package.json**, it will look like the following code snippet:
283
+
284
+ ``` json
285
+ {
286
+ "name": "java-mqtt-client-template",
287
+ "version": "0.0.1",
288
+ "description": "A template that generates a Java MQTT client using MQTT.",
289
+ "generator": {
290
+ "renderer": "react",
291
+ "apiVersion": "v1",
292
+ "generator": ">=1.10.0 <2.0.0",
293
+ "supportedProtocols": ["mqtt"],
294
+ "parameters": {
295
+ "server": {
296
+ "description": "The server you want to use in the code.",
297
+ "required": true
298
+ }
299
+ }
300
+ },
301
+ "scripts": {
302
+ "test:clean": "rimraf src/main/java/Client.java",
303
+ "test:generate": "asyncapi generate fromTemplate src/fixtures/asyncapi.yml ./ --output src/main/java --force-write --param server=dev",
304
+ "test:start": "gradle run -PmainClass=TestClient",
305
+ "test": "npm run test:clean && npm run test:generate && npm run test:start"
306
+ },
307
+ "dependencies": {
308
+ "@asyncapi/generator-react-sdk": "^0.2.25"
309
+ },
310
+ "devDependencies": {
311
+ "rimraf": "^5.0.0"
312
+ }
313
+ }
314
+ ```
315
+ Run `npm test` to see if everything is working.
316
+ ### 5. Create more channels
317
+
318
+ #### 5a. Creating more reusable components
319
+
320
+ Similar to the previous `TopicFunction` function we will create a function to make reusable components regardless of the number of channels in the asyncAPI document.
321
+
322
+ Create a **components** directory at the root of your project and create a file named `TopicFunction.js` and add the code snippet below:
323
+
324
+ ```js
325
+ /*
326
+ * This component returns a block of functions that users can use to send messages to specific topics.
327
+ * As input it requires a list of Channel models from the parsed AsyncAPI document.
328
+ */
329
+ export function TopicFunction({ channels }) {
330
+ const topicsDetails = getTopics(channels);
331
+ let functions = '';
332
+
333
+ topicsDetails.forEach((t) => {
334
+ functions += `
335
+ public void send${t.name}(String id) {
336
+ String topic = "${t.topic}";
337
+ try {
338
+ MqttMessage message = new MqttMessage(id.getBytes());
339
+ client.publish(topic, message);
340
+ System.out.println("${t.name} change sent: " + id);
341
+ } catch (MqttException e) {
342
+ e.printStackTrace();
343
+ }
344
+ }\n`;
345
+ });
346
+
347
+ return functions;
348
+ }
349
+
350
+ /*
351
+ * This function returns a list of objects, one for each channel, each containing two properties: `name` and `topic`.
352
+ * name - holds information about the `operationId` definedin the AsyncAPI document
353
+ * topic - holds information about the topic's address.
354
+ *
355
+ * It requires as input, a list of Channel models from the parsed AsyncAPI document.
356
+ */
357
+ function getTopics(channels) {
358
+ const channelsCanSendTo = channels
359
+ let topicsDetails = []
360
+
361
+ channelsCanSendTo.forEach((ch) => {
362
+ const topic = {}
363
+ const operationId = ch.operations().filterByReceive()[0].id()
364
+ topic.name = operationId.charAt(0).toUpperCase() + operationId.slice(1)
365
+ topic.topic = ch.address()
366
+
367
+ topicsDetails.push(topic)
368
+ })
369
+
370
+ return topicsDetails
371
+ }
372
+
373
+ ```
374
+
375
+ Import the `TopicFunction` component in your template code in **index.js** and add the template code to generate the functions for the topics which the `Temperature Service` application is subscribed to. In your case, the final version of your template code should look like this:
376
+
377
+ ```js
378
+ import { File, Text } from '@asyncapi/generator-react-sdk';
379
+ import { TopicFunction } from '../components/TopicFunction'
380
+
381
+ export default function ({ asyncapi, params }) {
382
+ let channels = asyncapi.channels().filterByReceive(); // Get all the channels that receive messages
383
+
384
+ // Generate Java code for each topic dynamically using TopicFunction
385
+ const topicMethods = TopicFunction({ channels }); // This will return Java methods as text
386
+
387
+ return (
388
+ <File name="Client.java">
389
+ {
390
+
391
+ `import org.eclipse.paho.client.mqttv3.*;
392
+
393
+ public class Client {
394
+ private static final String BROKER_URL = "${asyncapi.servers().get(params.server).url()}";
395
+ private static final String TOPIC = "temperature/changed";
396
+
397
+ private MqttClient client;
398
+
399
+ public Client() {
400
+ try {
401
+ // Generate a unique client ID
402
+ String clientId = MqttClient.generateClientId();
403
+
404
+ // Create and connect the MQTT client
405
+ client = new MqttClient(BROKER_URL, clientId);
406
+ MqttConnectOptions options = new MqttConnectOptions();
407
+ options.setCleanSession(true);
408
+
409
+ client.connect(options);
410
+ System.out.println("Connected to MQTT broker: " + BROKER_URL);
411
+ } catch (MqttException e) {
412
+ e.printStackTrace();
413
+ }
414
+ }
415
+
416
+ ${topicMethods}
417
+
418
+ public static void main(String[] args) {
419
+ Client serviceClient = new Client();
420
+
421
+ // Simulate sending a temperature change
422
+ //serviceClient.sendTemperatureDrop("Sensor-1: 25°C");
423
+ }
424
+ }`
425
+ }
426
+ </File>
427
+ );
428
+ }
429
+ ```
430
+
431
+ Now your directory should look like this:
432
+
433
+ ```
434
+ java-mqtt-client-template
435
+ ├── components
436
+ | └── TopicFunction.js
437
+ ├── src
438
+ | └── fixtures
439
+ | └── asyncapi.yml
440
+ │ └── main/java
441
+ | └── Client.java
442
+ | └── TestClient.java
443
+ ├── template
444
+ | └── index.js
445
+ └── package.json
446
+ └── build.gradle
447
+ ```
448
+
449
+ #### 5b. Update AsyncAPI document with more channels
450
+
451
+ Add the following AsyncAPI document to have more channels:
452
+
453
+ ```yaml
454
+ asyncapi: 2.6.0
455
+
456
+ info:
457
+ title: Temperature Service
458
+ version: 1.0.0
459
+ description: This service is in charge of processing all the events related to temperature.
460
+
461
+ servers:
462
+ dev:
463
+ url: tcp://test.mosquitto.org:1883
464
+ protocol: mqtt
465
+
466
+ channels:
467
+ temperature/dropped:
468
+ description: Notifies the user when the temperature drops past a certain point.
469
+ publish:
470
+ operationId: temperatureDrop
471
+ message:
472
+ description: Message that is being sent when the temperature drops past a certain point.
473
+ payload:
474
+ type: object
475
+ additionalProperties: false
476
+ properties:
477
+ temperatureId:
478
+ type: string
479
+
480
+ temperature/risen:
481
+ description: Notifies the user when the temperature rises past a certain point.
482
+ publish:
483
+ operationId: temperatureRise
484
+ message:
485
+ description: Message that is being sent when the temperature rises past a certain point.
486
+ payload:
487
+ type: object
488
+ additionalProperties: false
489
+ properties:
490
+ temperatureId:
491
+ type: string
492
+
493
+ components:
494
+ schemas:
495
+ temperatureId:
496
+ type: object
497
+ additionalProperties: false
498
+ properties:
499
+ temperatureId:
500
+ type: string
501
+
502
+ ```
503
+ #### 5c. Update TestClient.java
504
+ We must now update the **TestClient.java** file to test the different channels in the AsyncAPI document above. The tests will be similar to the previous ones you performed earlier. Paste the following code snippet into your **TestClient.java** file:
505
+
506
+ ```java
507
+ import java.util.Random;
508
+ import java.util.concurrent.TimeUnit;
509
+
510
+ public class TestClient {
511
+ public static void main(String[] args) {
512
+ Client client = new Client();
513
+ Random random = new Random();
514
+
515
+ int idLength = 8;
516
+ int minValue = (int) Math.pow(10, idLength - 1); // Minimum 8-digit number (e.g., 10000000)
517
+ int maxValue = (int) Math.pow(10, idLength) - 1; // Maximum 8-digit number (e.g., 99999999)
518
+ System.out.println("Validating generated generated Client.java");
519
+ System.out.println("Running tests in TestClient.java");
520
+ System.out.println("Sending temperature changes to the broker...");
521
+ System.err.println("\n");
522
+ while (true) {
523
+ int randomId = random.nextInt(maxValue - minValue + 1) + minValue;
524
+ client.sendTemperatureDrop(String.valueOf(randomId));
525
+ System.out.println("Temperature drop detected " + randomId + " sent to temperature/dropped");
526
+
527
+ client.sendTemperatureRise(String.valueOf(randomId));
528
+ System.out.println("Temperature risen detected " + randomId + " sent to temperature/risen");
529
+
530
+ try {
531
+ TimeUnit.SECONDS.sleep(1); // Sleep for 1 second
532
+ } catch (InterruptedException e) {
533
+ e.printStackTrace();
534
+ }
535
+ }
536
+ }
537
+ }
538
+ ```
539
+
540
+ Run `npm test` to validate that everything works as expected. You should see logs similar to the snippet below in your terminal:
541
+
542
+ ```cmd
543
+ Connected to MQTT broker: tcp://test.mosquitto.org:1883
544
+
545
+ Validating generated generated Client.java
546
+ Running tests in TestClient.java
547
+ Sending temperature changes to the broker...
548
+ TemperatureDrop change sent: 43289900
549
+ Temperature drop detected 43289900 sent to temperature/dropped
550
+ TemperatureRise change sent: 43289900
551
+ Temperature risen detected 43289900 sent to temperature/risen
552
+ ```
553
+
554
+ ## Where to go from here?
555
+
556
+ Great job completing this tutorial! You have learnt how to use an AsyncAPI file to create a Java MQTT template and used it with the Paho-MQTT library in Java to connect to an MQTT broker and publish messages.😃
557
+
558
+ If you want to tinker with a completed template and see what it would look like in production, check out the [Java-MQTT-client-template](https://github.com/ssala034/Java-MQTT-client-template). You can also check out the accompanying [article about creating MQTT client code](https://www.brainfart.dev/blog/asyncapi-codegen-python).
559
+
560
+ You can also check out the [MQTT beginners guide](https://medium.com/python-point/mqtt-basics-with-python-examples-7c758e605d4) tutorial to learn more about asynchronous messaging using MQTT.