├── .gitignore ├── guide.pdf ├── monitor.observer ├── src │ └── main │ │ └── java │ │ └── monitor │ │ └── observer │ │ ├── ServiceObserver.java │ │ └── DiagnosticDataPoint.java └── pom.xml ├── monitor.statistics ├── src │ └── main │ │ └── java │ │ └── monitor │ │ └── statistics │ │ ├── Statistician.java │ │ └── Statistics.java └── pom.xml ├── monitor.utils ├── src │ ├── test │ │ └── java │ │ │ └── monitor │ │ │ └── utils │ │ │ └── UtilsTest.java │ └── main │ │ └── java │ │ └── monitor │ │ └── utils │ │ └── Utils.java └── pom.xml ├── monitor.observer.beta ├── pom.xml └── src │ └── main │ └── java │ └── monitor │ └── observer │ └── beta │ └── BetaServiceObserver.java ├── monitor.persistence ├── pom.xml └── src │ └── main │ └── java │ └── monitor │ └── persistence │ └── StatisticsRepository.java ├── monitor.observer.alpha ├── pom.xml └── src │ └── main │ └── java │ └── monitor │ └── observer │ └── alpha │ └── AlphaServiceObserver.java ├── monitor.rest ├── pom.xml └── src │ └── main │ └── java │ └── monitor │ └── rest │ ├── MonitorServer.java │ └── StatisticsEntity.java ├── monitor ├── src │ └── main │ │ └── java │ │ └── monitor │ │ ├── Monitor.java │ │ └── Main.java └── pom.xml ├── README.md ├── pom.xml ├── LICENSE └── guide.adoc /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | */target 4 | build 5 | app 6 | mods 7 | -------------------------------------------------------------------------------- /guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipafx/demo-java-9-migration/HEAD/guide.pdf -------------------------------------------------------------------------------- /monitor.observer/src/main/java/monitor/observer/ServiceObserver.java: -------------------------------------------------------------------------------- 1 | package monitor.observer; 2 | 3 | public interface ServiceObserver { 4 | 5 | DiagnosticDataPoint gatherDataFromService(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /monitor.statistics/src/main/java/monitor/statistics/Statistician.java: -------------------------------------------------------------------------------- 1 | package monitor.statistics; 2 | 3 | import monitor.observer.DiagnosticDataPoint; 4 | 5 | public class Statistician { 6 | 7 | public Statistics emptyStatistics() { 8 | return Statistics.create(); 9 | } 10 | 11 | public Statistics compute(Statistics currentStats, Iterable dataPoints) { 12 | Statistics finalStats = currentStats; 13 | for (DiagnosticDataPoint dataPoint : dataPoints) 14 | finalStats = finalStats.merge(dataPoint); 15 | return finalStats; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /monitor.observer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | observer 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /monitor.utils/src/test/java/monitor/utils/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package monitor.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Optional; 6 | import java.util.function.Supplier; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.verifyZeroInteractions; 10 | import static org.mockito.Mockito.when; 11 | 12 | public class UtilsTest { 13 | 14 | @Test 15 | void earlySupplierProducesNonEmpty_findFirst_laterSuppliersNotCalled() throws Exception { 16 | @SuppressWarnings("unchecked") 17 | Supplier> notCalled = mock(Supplier.class); 18 | when(notCalled.get()).thenReturn(Optional.empty()); 19 | 20 | Utils.firstPresent(() -> Optional.of("present"), notCalled); 21 | 22 | verifyZeroInteractions(notCalled); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /monitor.utils/src/main/java/monitor/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package monitor.utils; 2 | 3 | import sun.misc.BASE64Encoder; 4 | 5 | import java.util.Arrays; 6 | import java.util.Optional; 7 | import java.util.function.Supplier; 8 | import java.util.stream.Stream; 9 | 10 | public class Utils { 11 | 12 | public static Stream stream(Optional optional) { 13 | return optional.map(Stream::of).orElseGet(Stream::empty); 14 | } 15 | 16 | @SafeVarargs 17 | public static Optional firstPresent(Supplier>... optionals) { 18 | return Arrays.stream(optionals) 19 | .map(Supplier::get) 20 | .flatMap(Utils::stream) 21 | .findFirst(); 22 | } 23 | 24 | public static String toBase64(String content) { 25 | return new BASE64Encoder().encode(content.getBytes()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /monitor.statistics/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | statistics 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.codefx.j9ms.monitor 20 | observer 21 | 1.0-SNAPSHOT 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /monitor.observer.beta/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | observer.beta 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.codefx.j9ms.monitor 20 | observer 21 | 1.0-SNAPSHOT 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /monitor.persistence/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | persistence 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.codefx.j9ms.monitor 20 | statistics 21 | 1.0-SNAPSHOT 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /monitor.observer.alpha/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | observer.alpha 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.codefx.j9ms.monitor 20 | observer 21 | 1.0-SNAPSHOT 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /monitor.persistence/src/main/java/monitor/persistence/StatisticsRepository.java: -------------------------------------------------------------------------------- 1 | package monitor.persistence; 2 | 3 | import monitor.statistics.Statistics; 4 | 5 | import java.util.Comparator; 6 | import java.util.Map.Entry; 7 | import java.util.Optional; 8 | 9 | public class StatisticsRepository { 10 | 11 | public Optional load() { 12 | return Optional.empty(); 13 | } 14 | 15 | public void store(Statistics statistics) { 16 | System.out.printf("Total liveness: %s (from %d data points)%n", 17 | statistics.totalLivenessQuota().livenessQuota(), 18 | statistics.totalLivenessQuota().dataPointCount()); 19 | statistics.livenessQuotaByService() 20 | .sorted(Comparator.comparing(Entry::getKey)) 21 | .forEach(serviceLiveness -> 22 | System.out.printf(" * %s liveness: %s (from %d data points)%n", 23 | serviceLiveness.getKey(), 24 | serviceLiveness.getValue().livenessQuota(), 25 | serviceLiveness.getValue().dataPointCount())); 26 | System.out.println(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /monitor.utils/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | utils 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.junit.jupiter 20 | junit-jupiter-api 21 | test 22 | 23 | 24 | org.mockito 25 | mockito-core 26 | test 27 | 28 | 29 | org.assertj 30 | assertj-core 31 | test 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /monitor.observer.beta/src/main/java/monitor/observer/beta/BetaServiceObserver.java: -------------------------------------------------------------------------------- 1 | package monitor.observer.beta; 2 | 3 | import monitor.observer.DiagnosticDataPoint; 4 | import monitor.observer.ServiceObserver; 5 | 6 | import java.time.ZonedDateTime; 7 | import java.util.Optional; 8 | import java.util.Random; 9 | 10 | public class BetaServiceObserver implements ServiceObserver { 11 | 12 | private static final Random RANDOM = new Random(); 13 | 14 | private final String serviceName; 15 | 16 | private BetaServiceObserver(String serviceName) { 17 | this.serviceName = serviceName; 18 | } 19 | 20 | public static Optional createIfBetaService(String service) { 21 | return Optional.of(service) 22 | // this check should do something more sensible 23 | .filter(s -> s.contains("beta")) 24 | .map(BetaServiceObserver::new); 25 | } 26 | 27 | @Override 28 | public DiagnosticDataPoint gatherDataFromService() { 29 | // this check should actually contact the serviceName 30 | boolean alive = RANDOM.nextFloat() > 0.1; 31 | return DiagnosticDataPoint.of(serviceName, ZonedDateTime.now(), alive); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /monitor.observer.alpha/src/main/java/monitor/observer/alpha/AlphaServiceObserver.java: -------------------------------------------------------------------------------- 1 | package monitor.observer.alpha; 2 | 3 | import monitor.observer.DiagnosticDataPoint; 4 | import monitor.observer.ServiceObserver; 5 | 6 | import java.time.ZonedDateTime; 7 | import java.util.Optional; 8 | import java.util.Random; 9 | 10 | public class AlphaServiceObserver implements ServiceObserver { 11 | 12 | private static final Random RANDOM = new Random(); 13 | 14 | private final String serviceName; 15 | 16 | private AlphaServiceObserver(String serviceName) { 17 | this.serviceName = serviceName; 18 | } 19 | 20 | public static Optional createIfAlphaService(String service) { 21 | return Optional.of(service) 22 | // this check should do something more sensible 23 | .filter(s -> s.contains("alpha")) 24 | .map(AlphaServiceObserver::new); 25 | } 26 | 27 | @Override 28 | public DiagnosticDataPoint gatherDataFromService() { 29 | // this check should actually contact the serviceName 30 | boolean alive = RANDOM.nextFloat() > 0.25; 31 | return DiagnosticDataPoint.of(serviceName, ZonedDateTime.now(), alive); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /monitor.rest/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | rest 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.codefx.j9ms.monitor 20 | statistics 21 | 1.0-SNAPSHOT 22 | 23 | 24 | org.codefx.j9ms.monitor 25 | utils 26 | 1.0-SNAPSHOT 27 | 28 | 29 | 30 | com.sparkjava 31 | spark-core 32 | 33 | 34 | com.fasterxml.jackson.core 35 | jackson-databind 36 | 37 | 38 | com.google.code.findbugs 39 | jsr305 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /monitor/src/main/java/monitor/Monitor.java: -------------------------------------------------------------------------------- 1 | package monitor; 2 | 3 | import monitor.observer.DiagnosticDataPoint; 4 | import monitor.observer.ServiceObserver; 5 | import monitor.persistence.StatisticsRepository; 6 | import monitor.statistics.Statistician; 7 | import monitor.statistics.Statistics; 8 | 9 | import java.util.List; 10 | 11 | import static java.util.Objects.requireNonNull; 12 | import static java.util.stream.Collectors.toList; 13 | 14 | class Monitor { 15 | 16 | private final List serviceObservers; 17 | private final Statistician statistician; 18 | private final StatisticsRepository repository; 19 | 20 | private Statistics currentStatistics; 21 | 22 | public Monitor( 23 | List serviceObservers, 24 | Statistician statistician, 25 | StatisticsRepository repository, 26 | Statistics initialStatistics) { 27 | this.serviceObservers = requireNonNull(serviceObservers); 28 | this.statistician = requireNonNull(statistician); 29 | this.repository = requireNonNull(repository); 30 | this.currentStatistics = requireNonNull(initialStatistics); 31 | } 32 | 33 | public void updateStatistics() { 34 | List newDataPoints = serviceObservers.stream() 35 | .map(ServiceObserver::gatherDataFromService) 36 | .collect(toList()); 37 | Statistics newStatistics = statistician.compute(currentStatistics, newDataPoints); 38 | currentStatistics = newStatistics; 39 | repository.store(newStatistics); 40 | } 41 | 42 | public Statistics currentStatistics() { 43 | return currentStatistics; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /monitor.observer/src/main/java/monitor/observer/DiagnosticDataPoint.java: -------------------------------------------------------------------------------- 1 | package monitor.observer; 2 | 3 | import java.time.ZonedDateTime; 4 | import java.util.Objects; 5 | 6 | import static java.lang.String.format; 7 | import static java.util.Objects.requireNonNull; 8 | 9 | public final class DiagnosticDataPoint { 10 | 11 | private final String service; 12 | private final ZonedDateTime timestamp; 13 | private final boolean alive; 14 | 15 | private DiagnosticDataPoint(String service, ZonedDateTime timestamp, boolean alive) { 16 | this.service = requireNonNull(service); 17 | this.timestamp = requireNonNull(timestamp); 18 | this.alive = alive; 19 | } 20 | 21 | public static DiagnosticDataPoint of(String service, ZonedDateTime timestamp, boolean alive) { 22 | return new DiagnosticDataPoint(service, timestamp, alive); 23 | } 24 | 25 | public String service() { 26 | return service; 27 | } 28 | 29 | public ZonedDateTime timestamp() { 30 | return timestamp; 31 | } 32 | 33 | public boolean alive() { 34 | return alive; 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | if (this == o) 40 | return true; 41 | if (o == null || getClass() != o.getClass()) 42 | return false; 43 | DiagnosticDataPoint that = (DiagnosticDataPoint) o; 44 | return alive == that.alive 45 | && Objects.equals(service, that.service) 46 | && Objects.equals(timestamp, that.timestamp); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(service, timestamp, alive); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return format("DiagnosticDataPoint{%s / %s: alive=%s}", service, timestamp, alive); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /monitor.rest/src/main/java/monitor/rest/MonitorServer.java: -------------------------------------------------------------------------------- 1 | package monitor.rest; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import monitor.statistics.Statistics; 6 | import spark.Spark; 7 | 8 | import javax.annotation.Nonnull; 9 | import javax.xml.bind.JAXBContext; 10 | import javax.xml.bind.JAXBException; 11 | import javax.xml.bind.Marshaller; 12 | import java.io.StringWriter; 13 | import java.util.function.Supplier; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | import static monitor.utils.Utils.toBase64; 17 | 18 | public class MonitorServer { 19 | 20 | private final Supplier statistics; 21 | 22 | private MonitorServer(Supplier statistics) { 23 | this.statistics = requireNonNull(statistics); 24 | } 25 | 26 | @Nonnull 27 | public static MonitorServer create(@Nonnull Supplier statistics) { 28 | return new MonitorServer(statistics); 29 | } 30 | 31 | @Nonnull 32 | public MonitorServer start() { 33 | Spark.get("/stats/json", (req, res) -> getStatisticsAsJson()); 34 | Spark.get("/stats/json64", (req, res) -> toBase64(getStatisticsAsJson())); 35 | Spark.get("/stats/xml", (req, res) -> getStatisticsAsXml()); 36 | return this; 37 | } 38 | 39 | private String getStatisticsAsJson() { 40 | return toJson(statistics.get()); 41 | } 42 | 43 | private static String toJson(Statistics stats) { 44 | try { 45 | ObjectMapper mapper = new ObjectMapper(); 46 | return mapper.writeValueAsString(StatisticsEntity.from(stats)); 47 | } catch (JsonProcessingException ex) { 48 | // don't do this in real live 49 | return ex.toString(); 50 | } 51 | } 52 | 53 | private String getStatisticsAsXml() { 54 | return toXml(statistics.get()); 55 | } 56 | 57 | private static String toXml(Statistics stats) { 58 | try { 59 | JAXBContext context = JAXBContext.newInstance(StatisticsEntity.class); 60 | Marshaller marshaller = context.createMarshaller(); 61 | marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 62 | StringWriter writer = new StringWriter(); 63 | marshaller.marshal(StatisticsEntity.from(stats), writer); 64 | return writer.toString(); 65 | } catch (JAXBException ex) { 66 | // don't do this in real live 67 | return ex.toString(); 68 | } 69 | } 70 | 71 | @Nonnull 72 | public MonitorServer shutdown() { 73 | Spark.stop(); 74 | return this; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServiceMonitor - Migration 2 | 3 | An example application for my book [_The Java Module System_](https://www.manning.com/books/the-java-module-system?a_aid=nipa&a_bid=869915cb). 4 | The _Service Monitor_ is an application that observes a hypothetical network of microservices by 5 | 6 | * contacting individual services 7 | * collecting and aggregating diagnostic data into statistics 8 | * persisting statistics 9 | * making statistics available via REST 10 | 11 | It is split into a number of subprojects that focus on specific concerns. 12 | Each project has its own directory that contains the known folder structure, e.g. `src/main/java`. 13 | 14 | It was developed as a Java 8 application and now needs to be made compatible with Java 9+ and then be modularized. 15 | 16 | ## Branches 17 | 18 | Each of the branches contains a different version of the project: 19 | 20 | * [`master`](https://github.com/CodeFX-org/demo-java-9-migration/tree/master): starting point, Java 8 21 | * [`migrated`](https://github.com/CodeFX-org/demo-java-9-migration/tree/migrated): compatible with Java 9 to 11 22 | * [`modularized`](https://github.com/CodeFX-org/demo-java-9-migration/tree/modularized): partially modularized 23 | 24 | ## Build and Execution 25 | 26 | In the project's root folder: 27 | 28 | * to build: `mvn clean install` 29 | * to run: `java -cp 'app/*' monitor.Main` 30 | * to contact REST endpoints: 31 | * `curl http://localhost:4567/stats/json` 32 | * `curl http://localhost:4567/stats/json64 | base64 -d` 33 | * `curl http://localhost:4567/stats/xml` 34 | 35 | ## Troubles 36 | 37 | Here's what you're facing. 38 | (If you get stuck, check [the guide](guide.adoc).) 39 | 40 | ### Migration to Java 9 41 | 42 | * internal `BASE64Encoder` is gone ~> use `Base64.getEncoder` instead 43 | * JAXB API is not present ~> add _java.xml.bind_ 44 | * Common annotations are not present ~> add _java.xml.ws.annotations_ 45 | * split package: `javax.annotation` between _java.xml.ws.annotations_ and _jsr-305_ ~> patch _java.xml.ws.annotations_ 46 | * old version of Mockito causes warnings ~> update to newer version, e.g. 2.8.47 47 | * application class loader is no longer a `URLClassLoader` ~> use system property `java.class.path` 48 | 49 | ### Migration to Java 10 50 | 51 | * even new version of Mockito may cause problems ~> update to yet newer version, e.g. 2.18.3 52 | * ASM dependency of Maven compiler plugin may cuase problems ~> update to newer version, e.g. 6.1.1 53 | 54 | ### Migrating to Java 11 55 | 56 | * Java EE modules were removed ~> add third-party dependencies 57 | -------------------------------------------------------------------------------- /monitor/src/main/java/monitor/Main.java: -------------------------------------------------------------------------------- 1 | package monitor; 2 | 3 | import monitor.observer.ServiceObserver; 4 | import monitor.observer.alpha.AlphaServiceObserver; 5 | import monitor.observer.beta.BetaServiceObserver; 6 | import monitor.persistence.StatisticsRepository; 7 | import monitor.rest.MonitorServer; 8 | import monitor.statistics.Statistician; 9 | import monitor.statistics.Statistics; 10 | import monitor.utils.Utils; 11 | 12 | import java.net.URL; 13 | import java.net.URLClassLoader; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.concurrent.Executors; 17 | import java.util.concurrent.ScheduledExecutorService; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.stream.Stream; 20 | 21 | import static java.util.Arrays.stream; 22 | import static java.util.stream.Collectors.joining; 23 | import static java.util.stream.Collectors.toList; 24 | 25 | public class Main { 26 | 27 | public static void main(String[] args) { 28 | logClassPathContent(); 29 | 30 | Monitor monitor = createMonitor(); 31 | MonitorServer server = MonitorServer 32 | .create(monitor::currentStatistics) 33 | .start(); 34 | 35 | ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); 36 | scheduler.scheduleAtFixedRate(monitor::updateStatistics, 1, 1, TimeUnit.SECONDS); 37 | scheduler.schedule(() -> { 38 | scheduler.shutdown(); 39 | server.shutdown(); 40 | }, 41 | 10, 42 | TimeUnit.SECONDS); 43 | } 44 | 45 | private static void logClassPathContent() { 46 | URLClassLoader classLoader = (URLClassLoader) Main.class.getClassLoader(); 47 | String message = stream(classLoader.getURLs()) 48 | .map(URL::toString) 49 | .map(url -> "\t" + url) 50 | .collect(joining("\n", "Class path content:\n", "\n")); 51 | System.out.println(message); 52 | } 53 | 54 | private static Monitor createMonitor() { 55 | List observers = Stream.of("alpha-1", "alpha-2", "alpha-3", "beta-1") 56 | .map(Main::createObserver) 57 | .flatMap(Utils::stream) 58 | .collect(toList()); 59 | Statistician statistician = new Statistician(); 60 | StatisticsRepository repository = new StatisticsRepository(); 61 | Statistics initialStatistics = repository.load().orElseGet(statistician::emptyStatistics); 62 | 63 | return new Monitor(observers, statistician, repository, initialStatistics); 64 | } 65 | 66 | private static Optional createObserver(String serviceName) { 67 | return Utils.firstPresent( 68 | () -> AlphaServiceObserver.createIfAlphaService(serviceName), 69 | () -> BetaServiceObserver.createIfBetaService(serviceName), 70 | () -> { 71 | System.out.printf("No observer for %s found.%n", serviceName); 72 | return Optional.empty(); 73 | }); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /monitor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | main 8 | 1.0-SNAPSHOT 9 | 10 | 11 | org.codefx.j9ms.monitor 12 | parent 13 | 1.0-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | 18 | 19 | org.codefx.j9ms.monitor 20 | observer 21 | 1.0-SNAPSHOT 22 | 23 | 24 | org.codefx.j9ms.monitor 25 | persistence 26 | 1.0-SNAPSHOT 27 | 28 | 29 | org.codefx.j9ms.monitor 30 | rest 31 | 1.0-SNAPSHOT 32 | 33 | 34 | org.codefx.j9ms.monitor 35 | observer.alpha 36 | 1.0-SNAPSHOT 37 | 38 | 39 | org.codefx.j9ms.monitor 40 | observer.beta 41 | 1.0-SNAPSHOT 42 | 43 | 44 | org.codefx.j9ms.monitor 45 | utils 46 | 1.0-SNAPSHOT 47 | 48 | 49 | 50 | 51 | 52 | 53 | maven-clean-plugin 54 | 55 | 56 | 57 | ${project.basedir}/../app 58 | false 59 | 60 | 61 | 62 | 63 | 64 | maven-jar-plugin 65 | 66 | ${project.basedir}/../app 67 | ${project.artifactId} 68 | 69 | 70 | 71 | maven-dependency-plugin 72 | 73 | 74 | copy-dependencies 75 | package 76 | 77 | copy-dependencies 78 | 79 | 80 | ${project.basedir}/../app 81 | true 82 | false 83 | true 84 | true 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /monitor.statistics/src/main/java/monitor/statistics/Statistics.java: -------------------------------------------------------------------------------- 1 | package monitor.statistics; 2 | 3 | import monitor.observer.DiagnosticDataPoint; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.Objects; 9 | import java.util.stream.Stream; 10 | 11 | import static java.lang.String.format; 12 | import static java.util.Objects.requireNonNull; 13 | 14 | public class Statistics { 15 | 16 | private final LivenessQuota totalLivenessQuota; 17 | private final Map livenessQuotaByService; 18 | 19 | private Statistics(LivenessQuota totalLivenessQuota, Map livenessQuotaByService) { 20 | this.totalLivenessQuota = requireNonNull(totalLivenessQuota); 21 | this.livenessQuotaByService = requireNonNull(livenessQuotaByService); 22 | } 23 | 24 | static Statistics create() { 25 | return new Statistics(LivenessQuota.zero(), new HashMap<>()); 26 | } 27 | 28 | Statistics merge(DiagnosticDataPoint dataPoint) { 29 | Map newStatistics = new HashMap<>(livenessQuotaByService); 30 | LivenessQuota oldServiceQuota = newStatistics.getOrDefault(dataPoint.service(), LivenessQuota.zero()); 31 | newStatistics.put(dataPoint.service(), oldServiceQuota.merge(dataPoint)); 32 | 33 | LivenessQuota newTotalQuota = totalLivenessQuota.merge(dataPoint); 34 | 35 | return new Statistics(newTotalQuota, newStatistics); 36 | } 37 | 38 | public LivenessQuota totalLivenessQuota() { 39 | return totalLivenessQuota; 40 | } 41 | 42 | public Stream> livenessQuotaByService() { 43 | return livenessQuotaByService.entrySet().stream(); 44 | } 45 | 46 | public static final class LivenessQuota { 47 | 48 | private final long dataPointCount; 49 | private final long aliveCount; 50 | 51 | LivenessQuota(long dataPointCount, long aliveCount) { 52 | this.dataPointCount = dataPointCount; 53 | this.aliveCount = aliveCount; 54 | } 55 | 56 | static LivenessQuota zero() { 57 | return new LivenessQuota(0,0); 58 | } 59 | 60 | private static long aliveAsLong(DiagnosticDataPoint dataPoint) { 61 | return dataPoint.alive() ? 1 : 0; 62 | } 63 | 64 | LivenessQuota merge(DiagnosticDataPoint dataPoint) { 65 | long newDataPointCount = this.dataPointCount + 1; 66 | long newAliveCount = aliveCount + aliveAsLong(dataPoint); 67 | return new LivenessQuota(newDataPointCount, newAliveCount); 68 | } 69 | 70 | public long dataPointCount() { 71 | return dataPointCount; 72 | } 73 | 74 | public double livenessQuota() { 75 | return aliveCount / (double) dataPointCount; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (this == o) 81 | return true; 82 | if (o == null || getClass() != o.getClass()) 83 | return false; 84 | LivenessQuota that = (LivenessQuota) o; 85 | return dataPointCount == that.dataPointCount 86 | && Double.compare(that.aliveCount, aliveCount) == 0; 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return Objects.hash(dataPointCount, aliveCount); 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | return format("LivenessQuota %s (%d data points)", livenessQuota(), dataPointCount); 97 | } 98 | 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.codefx.j9ms.monitor 8 | parent 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | UTF-8 14 | 1.8 15 | 1.8 16 | 17 | 18 | 19 | monitor 20 | monitor.observer 21 | monitor.observer.alpha 22 | monitor.observer.beta 23 | monitor.persistence 24 | monitor.rest 25 | monitor.statistics 26 | monitor.utils 27 | 28 | 29 | 30 | 31 | 32 | com.sparkjava 33 | spark-core 34 | 2.7.0 35 | 36 | 37 | com.fasterxml.jackson.core 38 | jackson-databind 39 | 2.8.10 40 | 41 | 42 | com.google.code.findbugs 43 | jsr305 44 | 3.0.2 45 | 46 | 47 | 48 | org.junit.jupiter 49 | junit-jupiter-api 50 | 5.0.2 51 | 52 | 53 | org.mockito 54 | mockito-core 55 | 2.2.1 56 | 57 | 58 | org.assertj 59 | assertj-core 60 | 3.8.0 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | maven-clean-plugin 69 | 3.1.0 70 | 71 | 72 | maven-dependency-plugin 73 | 3.1.1 74 | 75 | 76 | maven-compiler-plugin 77 | 3.7.0 78 | 79 | true 80 | 81 | 82 | 83 | maven-jar-plugin 84 | 3.1.0 85 | 86 | 87 | maven-surefire-plugin 88 | 2.19 89 | 90 | 91 | org.junit.platform 92 | junit-platform-surefire-provider 93 | 1.0.2 94 | 95 | 96 | org.junit.jupiter 97 | junit-jupiter-engine 98 | 5.0.2 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /monitor.rest/src/main/java/monitor/rest/StatisticsEntity.java: -------------------------------------------------------------------------------- 1 | package monitor.rest; 2 | 3 | import monitor.statistics.Statistics; 4 | import monitor.statistics.Statistics.LivenessQuota; 5 | 6 | import javax.annotation.Generated; 7 | import javax.xml.bind.annotation.XmlElement; 8 | import javax.xml.bind.annotation.XmlRootElement; 9 | import javax.xml.bind.annotation.XmlType; 10 | import java.util.List; 11 | import java.util.Map.Entry; 12 | import java.util.stream.Stream; 13 | 14 | import static java.util.stream.Collectors.toList; 15 | 16 | @Generated("NIPA-TYPING") 17 | @XmlRootElement(name = "statistics") 18 | @XmlType(propOrder = { "totalQuota", "quotas" }) 19 | public class StatisticsEntity { 20 | 21 | private QuotaEntity totalQuota; 22 | private QuotasEntity quotas; 23 | 24 | private StatisticsEntity() { } 25 | 26 | public static StatisticsEntity from(Statistics stats) { 27 | StatisticsEntity entity = new StatisticsEntity(); 28 | entity.setTotalQuota(QuotaEntity.from("total", stats.totalLivenessQuota())); 29 | entity.setQuotas(QuotasEntity.from(stats.livenessQuotaByService())); 30 | return entity; 31 | } 32 | 33 | @XmlElement(name = "total") 34 | public QuotaEntity getTotalQuota() { 35 | return totalQuota; 36 | } 37 | 38 | public void setTotalQuota(QuotaEntity totalQuota) { 39 | this.totalQuota = totalQuota; 40 | } 41 | 42 | @XmlElement(name = "services") 43 | public QuotasEntity getQuotas() { 44 | return quotas; 45 | } 46 | 47 | public void setQuotas(QuotasEntity quotas) { 48 | this.quotas = quotas; 49 | } 50 | 51 | @XmlRootElement(name = "services") 52 | public static class QuotasEntity { 53 | 54 | private List serviceQuotas; 55 | 56 | private QuotasEntity() { } 57 | 58 | public static QuotasEntity from(Stream> quotas) { 59 | QuotasEntity entity = new QuotasEntity(); 60 | entity.setServiceQuotas(quotas 61 | .map(serviceQuota -> QuotaEntity.from(serviceQuota.getKey(), serviceQuota.getValue())) 62 | .collect(toList())); 63 | return entity; 64 | } 65 | 66 | @XmlElement(name = "service") 67 | public List getServiceQuotas() { 68 | return serviceQuotas; 69 | } 70 | 71 | public void setServiceQuotas(List serviceQuotas) { 72 | this.serviceQuotas = serviceQuotas; 73 | } 74 | 75 | } 76 | 77 | @XmlRootElement(name = "quota") 78 | public static class QuotaEntity { 79 | 80 | private String serviceName; 81 | private long dataPointCount; 82 | private long aliveCount; 83 | 84 | private QuotaEntity() { 85 | } 86 | 87 | public static QuotaEntity from(String name, LivenessQuota quota) { 88 | QuotaEntity entity = new QuotaEntity(); 89 | entity.setServiceName(name); 90 | entity.setDataPointCount(quota.dataPointCount()); 91 | long count = Math.round(quota.dataPointCount() * quota.livenessQuota()); 92 | entity.setAliveCount(count); 93 | return entity; 94 | } 95 | 96 | @XmlElement(name = "name") 97 | public String getServiceName() { 98 | return serviceName; 99 | } 100 | 101 | public void setServiceName(String serviceName) { 102 | this.serviceName = serviceName; 103 | } 104 | 105 | @XmlElement(name = "dataPoints") 106 | public long getDataPointCount() { 107 | return dataPointCount; 108 | } 109 | 110 | public void setDataPointCount(long dataPointCount) { 111 | this.dataPointCount = dataPointCount; 112 | } 113 | 114 | @XmlElement(name = "aliveCount") 115 | public long getAliveCount() { 116 | return aliveCount; 117 | } 118 | 119 | public void setAliveCount(long aliveCount) { 120 | this.aliveCount = aliveCount; 121 | } 122 | 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /guide.adoc: -------------------------------------------------------------------------------- 1 | # Guide: To JAR Hell and Back 2 | 3 | Show that it works on Java 8: 4 | 5 | ```sh 6 | mvn -v 7 | mvn clean verify 8 | java -cp 'app/*' monitor.Main 9 | ``` 10 | 11 | In a different terminal: 12 | 13 | ```sh 14 | curl http://localhost:4567/stats/xml 15 | watch -n 1 curl http://localhost:4567/stats/xml 16 | ``` 17 | 18 | ## Migration 19 | 20 | ### Building on Java 9 21 | 22 | . start building with Java 9 23 | .. show `mvn -v` 24 | .. `sdk use java jdk-9` 25 | .. show `mvn -v` 26 | . create a profile for Java 9+ 27 | + 28 | ```xml 29 | 30 | 31 | java-9+ 32 | 33 | [9,) 34 | 35 | 36 | 37 | ``` 38 | . set `release` to 9 in profile 39 | + 40 | ```xml 41 | 42 | 9 43 | 44 | ``` 45 | 46 | #### Internal APIs 47 | 48 | . `mvn clean verify` 49 | . fix use of `sun.misc.BASE64Encoder` 50 | + 51 | ```java 52 | Base64.getEncoder().encodeToString(content.getBytes()); 53 | ``` 54 | 55 | . note warning message in _monitor.utils_ 56 | 57 | . fix warning messages in `UtilsTest` in _monitor.utils_ 58 | .. create a profile for Java 9 59 | .. show that `illegal-access=deny` disallows access 60 | .. configure Surefire to use `--add-opens` 61 | + 62 | ```xml 63 | 64 | 65 | 66 | maven-surefire-plugin 67 | 68 | 69 | --illegal-access=deny 70 | --add-opens=java.base/java.lang=ALL-UNNAMED 71 | 72 | 73 | 74 | 75 | 76 | ``` 77 | .. better, yet: update Mockito to `2.8.47` 78 | 79 | #### Java EE Modules 80 | 81 | . note error _package `javax.xml.bind.annotation` is not visible_ 82 | .. open `StatisticsEntity` 83 | 84 | . add comment `// from module java.xml.bind:` above `import XmlElement` 85 | . add _java.xml.bind_ module for annotations in _monitor.rest_ 86 | .. create a profile for Java 9 87 | .. configure compiler to use `--add-modules java.xml.bind` to fix _package `javax.xml.bind.annotation` is not visible_ 88 | + 89 | ```xml 90 | 91 | 92 | 93 | maven-compiler-plugin 94 | 95 | 96 | --add-modules=java.xml.bind 97 | 98 | 99 | 100 | 101 | 102 | ``` 103 | 104 | #### Patch Modules 105 | 106 | . observe _error: cannot find symbol_ for missing type `Generated` 107 | .. open `StatisticsEntity` 108 | . add _java.xml.ws.annotation_ to _monitor.rest_ to fix missing type `Generated` 109 | .. observe that error contains no hint of the containing module 110 | .. look up module in documentation 111 | .. add comment `// from module java.xml.ws.annotation:` above `import Generated` 112 | .. add _java.xml.ws.annotation_ 113 | + 114 | ```xml 115 | --add-modules=java.xml.ws.annotation 116 | ``` 117 | .. observe new missing types problem 118 | 119 | . patch _java.xml.ws.annotation_ with JSR classes 120 | + 121 | ```xml 122 | --patch-module=java.xml.ws.annotation=${settings.localRepository}/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar 123 | ``` 124 | . replace `maven.compiler.release` with `maven.compiler.source` and `maven.compiler.target` in parent POM 125 | 126 | ### Running on Java 9 127 | 128 | Start running with Java 9: 129 | 130 | ```sh 131 | java --class-path 'app/*' monitor.Main 132 | ``` 133 | 134 | . fix cast to `URLClassLoader` by replacing code in `logClassPathContent` with 135 | + 136 | ```java 137 | String[] classPath = System.getProperty("java.class.path").split(":"); 138 | String message = Arrays.stream(classPath) 139 | .map(url -> "\t" + url) 140 | .collect(joining("\n", "Class path content:\n", "\n")); 141 | System.out.println(message); 142 | ``` 143 | 144 | . add module `java.xml.bind` 145 | + 146 | ```sh 147 | java --add-modules java.xml.bind --class-path 'app/*' monitor.Main 148 | ``` 149 | 150 | . note that `--add-exports`, `--add-opens`, `--add-modules`, `--patch-module` usually carry from build to compile time 151 | 152 | ### Using Java 10 153 | 154 | . update to 10 with `sdk use java jdk-10` 155 | . show `mvn -v` 156 | . limit parent POM profile for Java 9+ to 9 and copy paste to create one for 10 157 | + 158 | ```xml 159 | 160 | java-10 161 | 162 | 10 163 | 164 | 165 | 10 166 | 10 167 | 168 | 169 | ``` 170 | . to use `var` somewhere, activate java-10 profile in IntelliJ and reimport 171 | . build with `mvn clean verify` 172 | . fix error in _monitor.utils_ by updating Mockito to 2.18.3 173 | . in parent POM's Java 10 profile update ASM to preempt compiler problems 174 | + 175 | ```xml 176 | 177 | 178 | 181 | 182 | maven-compiler-plugin 183 | 184 | 185 | org.ow2.asm 186 | asm 187 | 6.1.1 188 | 189 | 190 | 191 | 192 | 193 | ``` 194 | . run with 195 | + 196 | ```sh 197 | java --add-modules java.xml.bind --class-path 'app/*' monitor.Main 198 | ``` 199 | 200 | ### Using Java 11 201 | 202 | . update to 11 with `sdk use java jdk-11` 203 | . show `mvn -v` 204 | . in parent POM update compiler plugin to 3.8.0 + 205 | remove ASM update in profile `java-10` 206 | . in parent POM copy paste profile to create one for 11 207 | + 208 | ```xml 209 | 210 | java-11 211 | 212 | 11 213 | 214 | 215 | 11 216 | 11 217 | 218 | 219 | ``` 220 | . build with `mvn clean verify` 221 | . in _monitor.rest_'s POM, show how to edit profile to only be active for 9, 10: 222 | + 223 | ```xml 224 | java-9-01 225 | 226 | [9,10] 227 | 228 | ``` 229 | . then remove profile and add third-party dependencies in _monitor.rest_'s POM: 230 | + 231 | ```xml 232 | 233 | javax.annotation 234 | javax.annotation-api 235 | 1.3.1 236 | 237 | 238 | javax.xml.bind 239 | jaxb-api 240 | 2.3.0 241 | 242 | 243 | com.sun.xml.bind 244 | jaxb-core 245 | 2.3.0 246 | 247 | 248 | com.sun.xml.bind 249 | jaxb-impl 250 | 2.3.0 251 | 252 | ``` 253 | . run with 254 | + 255 | ```sh 256 | java --class-path 'app/*' monitor.Main 257 | ``` 258 | . observe that `xml` endpoint does not return anything, but `json` does 259 | . activate `java-11` profile, reimport, and launch from IntelliJ 260 | . in `MonitorServer` change `catch (JAXBException ex)` to `catch (Throwable ex)` and observe the error 261 | . add this dependency: 262 | + 263 | ```xml 264 | 265 | 266 | com.sun.activation 267 | javax.activation 268 | 1.2.0 269 | 270 | ``` 271 | + 272 | (Was not needed before because https://docs.oracle.com/javase/9/docs/api/java.xml.bind-summary.html[`java.xml.bind` requires it]) 273 | . point out illegal access warning (if present) and replace all dependencies in _monitor.rest_ with: 274 | + 275 | ```xml 276 | 277 | javax.annotation 278 | javax.annotation-api 279 | 1.3.2 280 | 281 | 282 | javax.xml.bind 283 | jaxb-api 284 | 2.3.1 285 | 286 | 287 | com.sun.xml.bind 288 | jaxb-core 289 | 2.3.0.1 290 | 291 | 292 | com.sun.xml.bind 293 | jaxb-impl 294 | 2.3.3 295 | 296 | ``` 297 | 298 | ## Modularization 299 | 300 | ### Preparation 301 | 302 | * in parent POM, replace compiler source and target 8 with release 11 303 | * remove version-specific profiles 304 | 305 | ### Get an Overview 306 | 307 | . see artifact dependencies 308 | + 309 | ```sh 310 | jdeps -summary -recursive --class-path 'app/*' --multi-release 11 app/main.jar 311 | ``` 312 | 313 | . limiting to _Monitor_ classes 314 | + 315 | ```sh 316 | jdeps -summary -recursive --class-path 'app/*' --multi-release 11 -include 'monitor.*' app/main.jar 317 | ``` 318 | 319 | . create a diagram 320 | + 321 | ```sh 322 | jdeps -summary -recursive --class-path 'app/*' --multi-release 11 -include 'monitor.*' --dot-output . app/main.jar 323 | dot -Tpng -O summary.dot 324 | gwenview summary.dot.png 325 | ``` 326 | 327 | . clean up graph 328 | + 329 | ```sh 330 | sed -i '/java.base/d' summary.dot 331 | sed -i 's/.jar//g' summary.dot 332 | dot -Tpng -O summary.dot 333 | gwenview summary.dot.png & 334 | ``` 335 | 336 | ### Start Bottom-Up 337 | 338 | . start with _monitor.utils_ 339 | .. modularize _monitor.utils_ 340 | + 341 | ```java 342 | module monitor.utils { 343 | exports monitor.utils; 344 | } 345 | ``` 346 | .. build with Maven 347 | .. observe that _monitor.utils_ is module: 348 | + 349 | ```sh 350 | jar --describe-module --file app/utils.jar 351 | ``` 352 | .. observe that the module works on the class path: 353 | + 354 | ```sh 355 | java --class-path 'app/*' monitor.Main 356 | ``` 357 | .. make it work on module path 358 | ... create `move-modules.sh` 359 | + 360 | ```sh 361 | #!/bin/bash 362 | set -e 363 | 364 | rm -rf mods 365 | mkdir mods 366 | ``` 367 | + 368 | and make it executable with `chmod +x move-modules.sh` 369 | ... add `mv app/utils.jar mods` to `move-modules.sh` 370 | ... create `run.sh` 371 | + 372 | ```sh 373 | #!/bin/bash 374 | set -e 375 | ``` 376 | ... try to run 377 | + 378 | ```sh 379 | # fails 380 | java --class-path 'app/*' --module-path mods monitor.Main 381 | # too noisy 382 | java --class-path 'app/*' --module-path mods \ 383 | --show-module-resolution monitor.Main 384 | # no utils 385 | java --class-path 'app/*' --module-path mods \ 386 | --show-module-resolution monitor.Main 387 | | grep utils 388 | # yes utils! 389 | java --class-path 'app/*' --module-path mods \ 390 | --add-modules monitor.utils \ 391 | --show-module-resolution monitor.Main 392 | | grep utils 393 | # launch 394 | java --class-path 'app/*' --module-path mods \ 395 | --add-modules monitor.utils \ 396 | monitor.Main 397 | ``` 398 | . continue with observers 399 | .. modularize _monitor.observer_: 400 | + 401 | ```java 402 | module monitor.observer { 403 | exports monitor.observer; 404 | } 405 | ``` 406 | .. modularize _monitor.observer.alpha_: 407 | + 408 | ```java 409 | module monitor.observer.alpha { 410 | requires monitor.observer; 411 | exports monitor.observer.alpha; 412 | } 413 | ``` 414 | .. modularize _monitor.observer.beta_: 415 | + 416 | ```java 417 | module monitor.observer.beta { 418 | requires monitor.observer; 419 | exports monitor.observer.beta; 420 | } 421 | ``` 422 | .. in _monitor.observer.alpha_, show with `mvn -X compiler` class/module path 423 | .. add `mv app/observer* mods` to `move-modules.sh` 424 | .. run 425 | + 426 | ```sh 427 | java --class-path 'app/*' --module-path mods \ 428 | --add-modules monitor.utils,monitor.observer.alpha,monitor.observer.beta \ 429 | monitor.Main 430 | ``` 431 | 432 | // TODO: during my last preparation, I changed a few things, but didn't 433 | 434 | . modularize statistics as preparation for _monitor.rest_ 435 | .. modularize _monitor.statistics_ 436 | + 437 | ```java 438 | module monitor.statistics { 439 | requires monitor.observer; 440 | exports monitor.statistics; 441 | } 442 | ``` 443 | .. add `mv app/statistics.jar mods` to `move-modules.sh` 444 | .. run 445 | + 446 | ```sh 447 | java --class-path 'app/*' --module-path mods \ 448 | --add-modules monitor.utils,monitor.observer.alpha,monitor.observer.beta,monitor.statistics \ 449 | monitor.Main 450 | ``` 451 | 452 | . modularize _monitor.rest_ in face of unmodularized dependencies 453 | .. create initial module descriptor 454 | + 455 | ```java 456 | module monitor.rest { 457 | requires java.xml.bind; 458 | 459 | requires monitor.utils; 460 | requires monitor.statistics; 461 | 462 | exports monitor.rest; 463 | } 464 | ``` 465 | .. use `jar11 --describe-module-file --file spark-core` etc to determine module names 466 | .. add `requires` for _spark.core_, _jackson.core_, _jackson.databind_ to module descriptor 467 | .. identify split package between _jsr305_ and _java.annotation_ 468 | .. add `requires java.annotation` 469 | .. patch package split: 470 | + 471 | ```xml 472 | 473 | 474 | 475 | maven-compiler-plugin 476 | 477 | 478 | --patch-module=java.annotation=${settings.localRepository}/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar 479 | 480 | 481 | 482 | 483 | 484 | ``` 485 | .. add to `move-modules.sh`: 486 | + 487 | ```sh 488 | mv app/jaxb-api.jar mods 489 | mv app/javax.activation.jar mods 490 | mv app/javax.annotation-api.jar mods 491 | mv app/spark-core.jar mods 492 | mv app/jackson-core.jar mods 493 | mv app/jackson-databind.jar mods 494 | ``` 495 | .. run 496 | + 497 | ```sh 498 | java --class-path 'app/*' --module-path mods \ 499 | --add-modules monitor.rest,monitor.observer.alpha,monitor.observer.beta \ 500 | monitor.Main 501 | ``` 502 | .. mention that `--patch-module` is not needed because annotations are not evaluated at run time 503 | .. observe run-time error in `watch` tab 504 | .. add `opens monitor.rest to java.xml.bind;` to _monitor.rest_ 505 | --------------------------------------------------------------------------------