Skip to content

Commit 2249db0

Browse files
authored
Merge pull request #761 from hexagontk/develop
Add extra and store modules
2 parents 100ba2a + f0d7cde commit 2249db0

File tree

73 files changed

+5276
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+5276
-121
lines changed

README.md

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
alt="GitHub Actions"
1616
src="https://github.com/hexagontk/hexagon/actions/workflows/release.yml/badge.svg" />
1717
</a>
18-
<a href="https://hexagontk.com/jacoco">
18+
<a href="https://hexagontk.com/stable/jacoco">
1919
<img src="https://hexagontk.com/stable/img/coverage.svg" alt="Coverage" />
2020
</a>
2121
<a href="https://search.maven.org/search?q=g:com.hexagontk">
@@ -25,7 +25,7 @@
2525

2626
<p align="center">
2727
<a href="https://hexagontk.com">Home Site</a> |
28-
<a href="https://hexagontk.com/quick_start">Quick Start</a>
28+
<a href="https://hexagontk.com/stable/quick_start">Quick Start</a>
2929
</p>
3030

3131
---
@@ -430,23 +430,6 @@ You can check more sample projects and snippets at the [examples page].
430430

431431
[examples page]: https://hexagontk.com/examples/example_projects
432432

433-
## Thanks
434-
435-
This project is supported by:
436-
437-
<a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=Hexagon-Toolkit">
438-
<img
439-
height="128px"
440-
src=
441-
"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_vertical_blue.svg">
442-
</a>
443-
444-
<a href="https://www.jetbrains.com/?from=Hexagon-Toolkit">
445-
<img
446-
height="96px"
447-
src="https://hexagontk.com/img/sponsors/jetbrains-variant-4.svg">
448-
</a>
449-
450433
## Status
451434

452435
The toolkit is properly tested. This is the coverage report:
@@ -456,8 +439,8 @@ The toolkit is properly tested. This is the coverage report:
456439
Performance is not the primary goal, but it is taken seriously. You can check performance numbers
457440
in the [TechEmpower Web Framework Benchmarks][benchmark].
458441

459-
[Coverage]: https://hexagontk.com/img/coverage.svg
460-
[CoverageReport]: https://hexagontk.com/jacoco
442+
[Coverage]: https://hexagontk.com/stable/img/coverage.svg
443+
[CoverageReport]: https://hexagontk.com/stable/jacoco
461444
[benchmark]: https://www.techempower.com/benchmarks
462445

463446
## Contribute
@@ -480,8 +463,6 @@ be up-to-date of project's news following [@hexagontk] on X (Twitter).
480463

481464
Thanks to all project's [contributors]!
482465

483-
[![CodeTriage](https://www.codetriage.com/hexagontk/hexagon/badges/users.svg)][CodeTriage]
484-
485466
[give it a star]: https://github.com/hexagontk/hexagon/stargazers
486467
[discussions]: https://github.com/hexagontk/hexagon/discussions/categories/q-a
487468
[@hexagontk]: https://twitter.com/hexagontk
@@ -490,7 +471,6 @@ Thanks to all project's [contributors]!
490471
[contributing]: https://github.com/hexagontk/hexagon/contribute
491472
[Organization Board]: https://github.com/orgs/hexagontk/projects/2
492473
[contributors]: https://github.com/hexagontk/hexagon/graphs/contributors
493-
[CodeTriage]: https://www.codetriage.com/hexagontk/hexagon
494474

495475
## License
496476

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ mapOf(
5959
apply(from = "gradle/certificates.gradle")
6060

6161
allprojects {
62-
version = "4.0.4"
62+
version = "4.1.0"
6363
group = "com.hexagontk"
6464
}
6565

extra/jul/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
# Module jul
3+
Contains logging utilities for the Java Util Logging package.
4+
5+
### Install the Dependency
6+
7+
=== "build.gradle"
8+
9+
```groovy
10+
implementation("com.hexagontk.extra:jul:$hexagonVersion")
11+
```
12+
13+
=== "pom.xml"
14+
15+
```xml
16+
<dependency>
17+
<groupId>com.hexagontk.extra</groupId>
18+
<artifactId>jul</artifactId>
19+
<version>$hexagonVersion</version>
20+
</dependency>
21+
```
22+
23+
# Package com.hexagontk.jul
24+
Provides helpers for the Java Util Logging library.

extra/jul/api/jul.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public final class com/hexagontk/jul/PatternFormat : java/util/logging/Formatter {
2+
public static final field COLOR_PATTERN Ljava/lang/String;
3+
public static final field PATTERN Ljava/lang/String;
4+
public fun <init> (ZZ)V
5+
public synthetic fun <init> (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
6+
public fun format (Ljava/util/logging/LogRecord;)Ljava/lang/String;
7+
}
8+

extra/jul/build.gradle.kts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
plugins {
3+
id("java-library")
4+
}
5+
6+
apply(from = "$rootDir/gradle/kotlin.gradle")
7+
apply(from = "$rootDir/gradle/lean.gradle")
8+
9+
apply(from = "$rootDir/gradle/publish.gradle")
10+
apply(from = "$rootDir/gradle/dokka.gradle")
11+
apply(from = "$rootDir/gradle/native.gradle")
12+
13+
group = "com.hexagontk.extra"
14+
description = "Java Util Logging utilities."
15+
16+
dependencies {
17+
"api"(project(":core"))
18+
}

extra/jul/main/PatternFormat.kt

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.hexagontk.jul
2+
3+
import com.hexagontk.core.text.AnsiColor.DEFAULT
4+
import com.hexagontk.core.text.AnsiColor.YELLOW
5+
import com.hexagontk.core.text.AnsiColor.BLUE
6+
import com.hexagontk.core.text.AnsiColor.BRIGHT_BLACK
7+
import com.hexagontk.core.text.AnsiColor.CYAN
8+
import com.hexagontk.core.text.AnsiColor.MAGENTA
9+
import com.hexagontk.core.text.AnsiColor.RED
10+
import com.hexagontk.core.text.AnsiEffect.BOLD
11+
import com.hexagontk.core.text.Ansi.RESET
12+
import com.hexagontk.core.text.eol
13+
import com.hexagontk.core.fail
14+
import com.hexagontk.core.toText
15+
import java.time.Instant
16+
import java.time.LocalDateTime
17+
import java.time.ZoneId
18+
import java.util.logging.Formatter
19+
import java.util.logging.Level
20+
import java.util.logging.LogRecord
21+
22+
/**
23+
* A Formatter implements [Formatter] provides support for formatting Logs.
24+
*
25+
* @property useColor Use colors in log messages.
26+
*/
27+
class PatternFormat(
28+
private val useColor: Boolean,
29+
private val messageOnly: Boolean = false,
30+
) : Formatter() {
31+
32+
internal companion object {
33+
private const val TIMESTAMP = "%tH:%<tM:%<tS,%<tL"
34+
private const val LEVEL = "%-5s"
35+
private const val THREAD = "[%-15s]"
36+
private const val LOGGER = "%-30s"
37+
38+
const val PATTERN = "$TIMESTAMP $LEVEL $THREAD $LOGGER | %s%n"
39+
const val COLOR_PATTERN =
40+
"$BRIGHT_BLACK$TIMESTAMP %s$LEVEL$RESET $MAGENTA$THREAD $CYAN$LOGGER$RESET | %s%n$RESET"
41+
}
42+
43+
private val pattern: String = if (useColor) COLOR_PATTERN else PATTERN
44+
45+
private val levelColors: Map<Level, String> = mapOf(
46+
Level.FINER to DEFAULT,
47+
Level.FINE to DEFAULT,
48+
Level.INFO to BLUE,
49+
Level.WARNING to YELLOW,
50+
Level.SEVERE to RED + BOLD
51+
)
52+
53+
private val levelNames: Map<Level, String> = mapOf(
54+
Level.FINER to "TRACE",
55+
Level.FINE to "DEBUG",
56+
Level.INFO to "INFO",
57+
Level.WARNING to "WARN",
58+
Level.SEVERE to "ERROR"
59+
)
60+
61+
override fun format(record: LogRecord): String {
62+
if (messageOnly)
63+
return record.message + "\n"
64+
65+
val instant = Instant.ofEpochMilli(record.millis)
66+
val dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
67+
val thrown = record.thrown
68+
val trace = when {
69+
thrown == null -> ""
70+
useColor -> "$eol${thrown.toText()}".replace("\n", "\n$RED")
71+
else -> "$eol${thrown.toText()}"
72+
}
73+
val level = record.level
74+
val levelName = levelNames[level] ?: fail
75+
val levelColor = levelColors[level] ?: BLUE
76+
val message = record.message
77+
val loggerName = record.loggerName
78+
val threadName = Thread.currentThread().name
79+
80+
return if (useColor)
81+
pattern.format(dateTime, levelColor, levelName, threadName, loggerName, message + trace)
82+
else
83+
pattern.format(dateTime, levelName, threadName, loggerName, message + trace)
84+
}
85+
}

extra/jul/main/SystemStreamHandler.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.hexagontk.jul
2+
3+
import java.io.PrintStream
4+
import java.util.logging.Formatter
5+
import java.util.logging.Level
6+
import java.util.logging.LogRecord
7+
import java.util.logging.StreamHandler
8+
9+
/**
10+
* Create a StreamHandler with a given [Formatter].
11+
*
12+
* @param handlerFormatter Formatter used by the log handler.
13+
*/
14+
internal class SystemStreamHandler(
15+
handlerFormatter: Formatter,
16+
stream: PrintStream = System.out
17+
) : StreamHandler() {
18+
19+
override fun publish(record: LogRecord) {
20+
super.publish(record)
21+
flush()
22+
}
23+
24+
init {
25+
setOutputStream(stream)
26+
formatter = handlerFormatter
27+
level = Level.ALL
28+
}
29+
}

extra/jul/main/module-info.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* This module holds utilities used in other libraries of the toolkit. Check the packages'
3+
* documentation for more details. You can find a quick recap of the main features in the sections
4+
* below.
5+
*/
6+
module com.hexagontk.jul {
7+
8+
requires transitive java.logging;
9+
requires transitive com.hexagontk.core;
10+
11+
exports com.hexagontk.jul;
12+
}

extra/jul/test/PatternFormatTest.kt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.hexagontk.jul
2+
3+
import com.hexagontk.core.fail
4+
import com.hexagontk.core.text.AnsiColor
5+
import org.junit.jupiter.api.Assertions
6+
import org.junit.jupiter.api.Test
7+
import java.lang.RuntimeException
8+
import java.util.logging.Level.INFO
9+
import java.util.logging.Level.SEVERE
10+
import java.util.logging.LogRecord
11+
12+
internal class PatternFormatTest {
13+
14+
@Test fun `Formatting messages with 'printf' special characters works correctly`() {
15+
val message = "Message with '%'"
16+
17+
val colorFormat = PatternFormat(true)
18+
val colorMessage = colorFormat.format(LogRecord(INFO, message))
19+
Assertions.assertTrue(colorMessage.contains(message))
20+
Assertions.assertTrue(colorMessage.contains(AnsiColor.BLUE))
21+
22+
val plainFormat = PatternFormat(false)
23+
val plainMessage = plainFormat.format(LogRecord(INFO, message))
24+
Assertions.assertTrue(plainMessage.contains(message))
25+
Assertions.assertFalse(plainMessage.contains(AnsiColor.BLUE))
26+
}
27+
28+
@Test fun `Formatting error messages render stack traces`() {
29+
val message = "Message with '%'"
30+
val record = LogRecord(SEVERE, message)
31+
record.thrown = RuntimeException("Tested failure")
32+
33+
val colorMessage = PatternFormat(true).format(record)
34+
Assertions.assertTrue(colorMessage.contains(message))
35+
Assertions.assertTrue(colorMessage.contains(AnsiColor.RED))
36+
Assertions.assertTrue(colorMessage.contains("Tested failure"))
37+
Assertions.assertTrue(colorMessage.contains(RuntimeException::class.qualifiedName ?: fail))
38+
Assertions.assertTrue(colorMessage.contains(this::class.qualifiedName ?: fail))
39+
40+
val plainMessage = PatternFormat(false).format(record)
41+
Assertions.assertTrue(plainMessage.contains(message))
42+
Assertions.assertFalse(plainMessage.contains(AnsiColor.RED))
43+
Assertions.assertTrue(plainMessage.contains("Tested failure"))
44+
Assertions.assertTrue(plainMessage.contains(RuntimeException::class.qualifiedName ?: fail))
45+
Assertions.assertTrue(plainMessage.contains(this::class.qualifiedName ?: fail))
46+
}
47+
48+
@Test fun `Formatting messages without logging fields works correctly`() {
49+
val message = "Message with '%'"
50+
51+
val colorFormat = PatternFormat(useColor = true, messageOnly = true)
52+
val colorMessage = colorFormat.format(LogRecord(INFO, message))
53+
Assertions.assertTrue(colorMessage.contains(message))
54+
Assertions.assertFalse(colorMessage.contains("INFO"))
55+
Assertions.assertFalse(colorMessage.contains(AnsiColor.BLUE))
56+
57+
val plainFormat = PatternFormat(useColor = false, messageOnly = true)
58+
val plainMessage = plainFormat.format(LogRecord(INFO, message))
59+
Assertions.assertTrue(plainMessage.contains(message))
60+
Assertions.assertFalse(colorMessage.contains("INFO"))
61+
Assertions.assertFalse(plainMessage.contains(AnsiColor.BLUE))
62+
}
63+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.hexagontk.jul
2+
3+
import org.junit.jupiter.api.Test
4+
import java.io.ByteArrayOutputStream
5+
import java.io.PrintStream
6+
import java.util.logging.Level.INFO
7+
import java.util.logging.LogRecord
8+
9+
class SystemStreamHandlerTest {
10+
11+
@Test fun `Log records can be printed to a stream`() {
12+
val output = ByteArrayOutputStream()
13+
val record = LogRecord(INFO, "message")
14+
15+
SystemStreamHandler(PatternFormat(false), PrintStream(output)).publish(record)
16+
assert(output.toByteArray().decodeToString().contains("message"))
17+
output.flush()
18+
19+
val systemOut = System.out
20+
System.setOut(PrintStream(output))
21+
SystemStreamHandler(PatternFormat(false)).publish(record)
22+
assert(output.toByteArray().decodeToString().contains("message"))
23+
System.setOut(systemOut)
24+
}
25+
}

extra/scheduler/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
# Module scheduler
3+
Provides repeated tasks execution based on [Cron] expressions for Hexagon framework. It uses the
4+
[Cron-utils Java Library].
5+
6+
!!! Note
7+
In some platforms (i.e.: Kubernetes) there is a way to execute repeated tasks, you could take
8+
advantage of them, as using your own service will raise problems scaling those services'
9+
instances (you will have to coordinate them).
10+
11+
This feature does not include any sort of synchronization if you have many instances of a scheduler
12+
service. If you want your scheduled jobs to be executed just once, you have to take care of
13+
synchronization yourself.
14+
15+
### Install Dependency
16+
17+
=== "build.gradle"
18+
19+
```groovy
20+
repositories {
21+
mavenCentral()
22+
}
23+
24+
implementation("com.hexagontk.extra:scheduler:$hexagonVersion")
25+
```
26+
27+
=== "pom.xml"
28+
29+
```xml
30+
<dependency>
31+
<groupId>com.hexagontk.extra</groupId>
32+
<artifactId>scheduler</artifactId>
33+
<version>$hexagonVersion</version>
34+
</dependency>
35+
```
36+
37+
### Example
38+
You can check a usage example in the following code:
39+
40+
@code extra/scheduler/test/CronSchedulerTest.kt?sample
41+
42+
# Package com.hexagontk.scheduler
43+
Classes for scheduling blocks of code repeatedly based on a [Cron] expression.
44+
45+
[Cron]: https://en.wikipedia.org/wiki/Cron
46+
[Cron-utils Java Library]: http://cron-parser.com

0 commit comments

Comments
 (0)