├── .gitignore ├── README.asciidoc ├── README.pdf ├── deploy.sh ├── library-client ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── libraryclient │ │ │ └── LibraryClientApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── example │ └── libraryclient │ └── LibraryClientApplicationTests.java ├── library-service ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── example │ │ │ └── libraryservice │ │ │ ├── Book.java │ │ │ ├── BookRepository.java │ │ │ ├── BookRestConfigurationJava.java │ │ │ ├── BookRestConfigurationKotlin.kt │ │ │ ├── BookRestController.java │ │ │ ├── LibraryServiceApplication.java │ │ │ ├── SampleBookInitializer.java │ │ │ └── SecurityConfiguration.java │ └── resources │ │ └── application.properties │ └── test │ └── kotlin │ └── com │ └── example │ └── libraryservice │ └── LibraryServiceApplicationTests.kt ├── pdf.sh └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | 11 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 12 | !/.mvn/wrapper/maven-wrapper.jar 13 | .idea/ 14 | library-root.iml 15 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | [[Reactive Spring]] 2 | 3 | == Reactive Spring 4 | by Josh Long (@starbuxman), Spring Developer Advocate, Pivotal 5 | 6 | 7 | == The Missing Metaphor 8 | 9 | Reactive programming is an approach to writing software that embraces asynchronous IO. Asynchronous I/O is a small idea that portends big changes for software. The idea is simple: alleviate inefficient resource utilization by reclaiming resources that would otherwise be idle as they waited for I/O activity. Asynchronous IO inverts the normal design IO processing: the clients are notified of new data instead of asking for it; this frees the client to do other things while waiting for new notifications. There is always the risk that too many notifications will overwhelm a client. A client must be able to push back, rejecting work it can't handle. This is a fundamental aspect of flow control in distributed systems. In reactive programming, the ability of the client to signal how much work it can manage is called _backpressure_. Many projects - like Vert.x, Akka Streams, and RxJava - support reactive programming. The Spring team has a project called http://projectreactor.io[Reactor]. There's common ground across these different approaches extracted into a de-facto standard, http://www.reactive-streams.org[the Reactive Streams initiative]. The Reactive Streams initiative defines four types: 10 | 11 | The `Publisher` is a producer of values that may eventually arrive. A `Publisher` produces values of type `T`. 12 | 13 | .the Reactive Streams `Publisher`. 14 | ==== 15 | [source,java,indent=0] 16 | ---- 17 | package org.reactivestreams; 18 | 19 | public interface Publisher { 20 | 21 | void subscribe(Subscriber s); 22 | } 23 | ---- 24 | ==== 25 | 26 | The `Subscriber` subscribes to a `Publisher`, receiving notifications on any new values of type `T`. 27 | 28 | .the Reactive Streams `Subscriber`. 29 | ==== 30 | [source,java,indent=0] 31 | ---- 32 | package org.reactivestreams; 33 | 34 | public interface Subscriber { 35 | 36 | public void onSubscribe(Subscription s); 37 | 38 | public void onNext(T t); 39 | 40 | public void onError(Throwable t); 41 | 42 | public void onComplete(); 43 | } 44 | ---- 45 | ==== 46 | 47 | When a `Subscriber` subscribes to a `Publisher`, it results in a `Subscription`. 48 | 49 | .The Reactive Streams `Subscription`. 50 | ==== 51 | [source,java,indent=0] 52 | ---- 53 | package org.reactivestreams; 54 | 55 | public interface Subscription { 56 | 57 | public void request(long n); 58 | 59 | public void cancel(); 60 | } 61 | ---- 62 | ==== 63 | 64 | A `Publisher` that is also a `Subscriber` is called a `Processor`. 65 | 66 | .The Reactive Streams `Processor`. 67 | ==== 68 | [source,java,indent=0] 69 | ---- 70 | package org.reactivestreams; 71 | 72 | public interface Processor extends Subscriber, Publisher { 73 | } 74 | ---- 75 | ==== 76 | 77 | The specification is not meant to be a prescription for the implementations, instead defining types for interoperability. The Reactive Streams types eventually found their way into Java 9 as one to one semantically equivalent interfaces in the `java.util.concurrent.Flow` class. 78 | 79 | == Reactor 80 | The Reactive Streams types are not enough; you'll need higher order implementations to support operators like filtering and transformation. Pivotal's Reactor project is a good choice here; it builds on top of the Reactive Streams specification. It provides two specializations of the `Publisher`. The first, `Flux`, is a Publisher that produces zero or more values. It's unbounded. The second, `Mono`, is a `Publisher` that produces zero or one value. They're both publishers and you can treat them that way, but they go much further than the Reactive Streams specification. They both provide operators, ways to process a stream of values. Reactor types compose nicely - the output of one thing can be the input to another. 81 | 82 | == Reactive Spring 83 | As useful as project Reactor is, it's only a foundation. Our applications need to talk to data sources. They need to produce and consume HTTP, SSE and WebSocket endpoints. They support authentication and authorization. Spring provides these things. If Reactor gives us the missing metaphor, Spring helps us all speak the same language. 84 | 85 | Spring Framework 5.0 was released in September 2017. It builds on Reactor and the Reactive Streams specification. It includes a new reactive runtime and component model called https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web-reactive.html#webflux[Spring WebFlux]. Spring WebFlux does not depend on or require the Servlet APIs to work. It ships with adapters that allow it to work on top of a Servlet-engine, if need be, but it's not required. It also provides a Netty-based web server. Spring Framework 5, which works with a baseline of Java 8 and Java EE 7 - is the foundation for changes in much of the Spring ecosystem. 86 | 87 | == A Bootiful Application 88 | Let's look at an example. We'll build a simple Spring Boot 2.0 application that, oh, I don't know... How about we build a service to manage books? We could call the project _Library_ or something like that. Go to the http://start.spring.io[Spring Initializr]. Make sure that some version of Spring Boot 2.0 (or later) is selected in the version drop down menu. We're writing a service to manage access to books in the library, so give this project the artifact ID `library-service`. Select `Reactive Web`, `Actuator`, `Reactive MongoDB`, `Reactive Security`, and `Lombok`. I chose to use the Kotlin language, even if most of the project we'll build is in Java. You can keep Java artifacts collocated in a Kotlin project. Click _Generate_ and it'll download an archive; unzip it and open it in your favorite IDE that supports Java 8 (or later), Kotlin (optionally) and Maven. While we could've chosen Gradle at the Spring Initializr, I've chosen Maven for the purposes of this article. 89 | 90 | Our stock standard Spring Boot application has an entry class that looks like this. 91 | 92 | .the empty husk of a new Spring Boot project. 93 | ==== 94 | [source,java,indent=0] 95 | ---- 96 | include::./library-service/src/main/kotlin/com/example/libraryservice/LibraryServiceApplication.java[] 97 | ---- 98 | ==== 99 | 100 | == Data Access with Reactive Spring Data modules 101 | 102 | Spring Data Kay is the largest update to Spring Data since its inception. This release debuts support, where supported in the underlying data stores (MongoDB, Cassandra, Redis and Couchbase), for reactive data access. It introduces new reactive repository and template implementations. We've got the reactive MongoDB driver and Spring Data module on the classpath so let's use them to manage some data. Create a new entity called `Book`. 103 | 104 | .a MongoDB `@Document` entity, `Book` 105 | ==== 106 | [source,java,indent=0] 107 | ---- 108 | include::./library-service/src/main/kotlin/com/example/libraryservice/Book.java[] 109 | ---- 110 | ==== 111 | 112 | Create a Spring Data repository to support the data management lifecycle of the entity. This should look very familiar for anybody who's ever used Spring Data, except that the repository supports _reactive_ interactions: methods return `Publisher` types, and input can be given as `Publisher` instances. 113 | 114 | .A reacive Spring Data MongoDB repository 115 | ==== 116 | [source,java,indent=0] 117 | ---- 118 | include::./library-service/src/main/kotlin/com/example/libraryservice/BookRepository.java[] 119 | ---- 120 | ==== 121 | 122 | 123 | == Install Some Sample Data with an `ApplicationRunner` 124 | With that we have enough to install some sample data (just for our demo). Spring Boot invokes the `#run(ApplicationArguments)` method when the application has started, passing in wrappers for the arguments (`String [] args`) into the application. Let's create an `ApplicationRunner` that deletes all the data in the data source, then emits a few book titles, then maps them to `Book` entities, and then persists those books; then queries all the records in the data source and then prints out everything. 125 | 126 | .an `ApplicationRunner` to write data 127 | ==== 128 | [source,java,indent=0] 129 | ---- 130 | include::./library-service/src/main/kotlin/com/example/libraryservice/SampleBookInitializer.java[] 131 | ---- 132 | ==== 133 | 134 | The example looks at the titles of various books, and one of (the possibly numerous) book's authors, and writes them to the database. First the strings are split by the `|` delimiter. Then the title and book author are used to create a `Book`. Then the records are saved to the data source, MongoDB. The result of the `save` operation is a `Mono`. Something needs to subscribe to each of those resulting `Publisher`s, so we use the `flatMap` operator. Then, we switch tracks, turning focusing on the results of finding all records and then logging them for inspection. 135 | 136 | This code defines a pipeline; each operator defines a stage in a pipeline. The pipeline is not _hot_, or _eager_. We need to activate the pipeline by subscribing to it. The `Publisher` only defines one type of subscription, but Reactor defines a few overloads that provide the same lifecycle hooks as overriding individual methods in a fully formed `Subscriber` would. There are hooks to process each emitted value, as well any exceptions thrown, among other things. 137 | 138 | == Reactive and in a Rush? Use a `Scheduler`! 139 | 140 | Were you to put a breakpoint in any of the lambdas in the previous code listing and then inspect `Thread.currentThread().getName()`, you'd see that the the thread on which processing is running is different than the main thread (which is named `main`). Reactor defers to a `Scheduler` implementation for its processing. You can specify the default global `Scheduler` you'd like to use by calling `Schedulers.setFactory(Factory)`. You can specify on which thread a particular `Publisher` should run when it subscribes by specifying `Mono#subscribeOn(Scheduler)` or `Flux#subscribeOn(Scheduler)`. 141 | 142 | == Spring WebFlux 143 | 144 | Now that we've got data in the data source, let's stand up a REST API. We'll use Spring WebFlux, a brand new reactive web runtime and component model. Spring WebFlux does not depend on the Servlet specification. It can work independently, with a Netty-based web server. It is designed, from the bottom up, to work with `Publisher` instances. 145 | 146 | == REST with Spring WebFlux 147 | 148 | We can use Spring MVC style controllers, like this: 149 | 150 | .a Spring MVC style REST API. 151 | ==== 152 | [source,java,indent=0] 153 | ---- 154 | include::./library-service/src/main/kotlin/com/example/libraryservice/BookRestController.java[] 155 | ---- 156 | ==== 157 | 158 | NOTE: Spring has the concept of profiles. Profiles are labels, or tags, essentially, for Spring beans. Beans in a given profile don't exist unless that profile is activated. The easiest way to activate a profile is to use a command line argument when running the `java` command. If you wanted to activate all the beans under the `profile1` and `profile2` profiles, you'd use an incantation like this: `java -Dspring.profiles.active=profile1,profile2 -jar ..`. The benefit of the profile, in this case, is that we can have the same HTTP endpoints implemented three different ways in the same codebase and only activate one at a time. 159 | 160 | This controller should look familiar to anyone who's ever used Spring MVC. It may be familiar, but it is _not_ Spring MVC. We're using a new reactive runtime called Spring WebFlux. The annotations are the same, but the rules are sometimes different. 161 | 162 | == Functional Reactive Endpoints 163 | 164 | The last code listing demonstrates a controller. Spring Web Flux controllers define endpoint handlers and endpoint mappings through declarative annotations. The annotations describe how the routing for a given endpoints is to be handled. They're sophisticated, but ultimately limited to whatever the framework itself can do with those annotations. If you want more flexible request matching capabilities, you can use Spring WebFlux functional reactive endpoints. 165 | 166 | .The same endpoints reworked as funtional reactive endpoints in Java. Run this using the `frp-java` profile. 167 | ==== 168 | [source,java,indent=0] 169 | ---- 170 | include::./library-service/src/main/kotlin/com/example/libraryservice/BookRestConfigurationJava.java[] 171 | ---- 172 | ==== 173 | 174 | The functional reactive style lets you express HTTP endpoints as request predicates mapped to a handler class. The handler class implementation is easily expressed as concise Java lambdas. You can use the default request predicates or provide your to gain full control over how requests are matched and dispatched. 175 | 176 | We produce a result and pass it to the `body(Publisher)` method, along with a class literal. We need the class literal to help the engine figure out what type of message framing it should do. Remember, that `Publisher` _might_ produce a TRILLION records - it may never stop! The producer can't afford to wait until all records have been produced and _then_ marshal the record from an object to JSON. So, it marhsals each record as soon as it's got it. We need to tell it what kind of message to look out for. In the Spring MVC style controllers, the return value (a `Publisher`) in the handler methods encodes its generic parameter, `T`, and the engine can retrieve that generic parameter using reflection. The engine can _not_ do the same thing for the instance variable passed into the `body` method as a parameter since there's no easy way to retrieve the generic signature of instance variables. We call this limitation _type erasure_. The type literal gets us past this restriction. 177 | 178 | If you're using the Kotlin language, things are even more concise thanks to a Kotlin-language DSL that also ships as part of Spring Framework 5. The Kotlin DSL requires less code and also supports retrieving the generic parameter thanks to runtime reification of inline methods. Here are the same endpoints reimplemented using the Kotlin-language DSL: 179 | 180 | .Here are the same endpoints using the Kotlin-language DSL 181 | ==== 182 | [source,java,indent=0] 183 | ---- 184 | include::./library-service/src/main/kotlin/com/example/libraryservice/BookRestConfigurationKotlin.kt[] 185 | ---- 186 | ==== 187 | 188 | 189 | == Spring Security 190 | 191 | I know what you're thinking, but its *still* not quite ready for production. We need to address security. Spring Security supports a rich set of integrations with all manner of identity providers. It supports authentication by propagating a security context so that application level code - method invocations, HTTP requests, etc. - have easy access to the context. The context has historically been implemented with a `ThreadLocal`. Thread local state doesn't make a lot of sense in a reactive world. Reactor provides a `Context` object, which acts as a sort of dictionary. Spring Security 5.0's reactive support propagates its security context using this mechanism. Parallel, reactive type hierarchies have been introduced to support non-blocking authentication and authorization. You don't have to worry about much of this nuance. I just think it's dope. All we need to know is that in the reactive world, authentication is handled by an object of type `ReactiveAuthenticationManager` which has a simple job: given an `Authentication` attempt, return a `Mono` (indicating whether the authentication attempt succeeded) or throw an `Exception`. 192 | 193 | One implementation of the `ReactiveAuthenticationManager` supports delegating to a user-provided object of type `MapReactiveUserDetailsService`. The `MapReactiveUserDetailsService` connects your custom username and password store to Spring Security's authentication. You might have a database table called `USERS` or just a hardcoded `Map` of users. By default, Spring Security locks down the whole application and installs HTTP BASIC authentication. Any attempt at calling any endpoint will fail unless we provide credentials. By default all authenticated principals can access all endpoints. Let's establish introduce a few users of various roles. All users will have the `USER` role, but only a privileged few will have the `ADMIN` role. In this newly secured world, let's say that all users will be able to view the books they've written, but only those with the `ADMIN` role will be able to see _all_ the books. (Let's ignore for now whether this domain makes any sense!) 194 | 195 | .The Spring Security configuration 196 | ==== 197 | [source,java,indent=0] 198 | ---- 199 | include::./library-service/src/main/kotlin/com/example/libraryservice/SecurityConfiguration.java[] 200 | ---- 201 | ==== 202 | 203 | Let's try making an HTTP BASIC authenticated call to the service. I'll make the first request as `jlong`, a regular `USER`. 204 | 205 | .`curl` the endpoint as `jlong` 206 | ==== 207 | [source,java,indent=0] 208 | ---- 209 | curl -ujlong:pw http://localhost:8080/books/jlong 210 | ---- 211 | ==== 212 | 213 | It won't work if I try to access `http://localhost:8080/books/rwinch`, though. Only `ADMIN` role users can access other endpoints. 214 | 215 | .`curl` the endpoint as `rwinch` 216 | ==== 217 | [source,java,indent=0] 218 | ---- 219 | curl -urwinch:pw http://localhost:8080/books 220 | ---- 221 | ==== 222 | 223 | == Deployment 224 | 225 | Our application is secure and observable. _Now_ we can deploy it. This is a natural thing to run in a cloud provider like Cloud Foundry, an open-source Apache 2 licensed cloud platform that's optomized for the continuous management of applications. It sits at a level (or two) above cloud infrastructure. It is infrastructure agnostic, running on local cloud providers like OpenStack and vSphere or on public cloud providers like Amazon Web Services, Google Cloud, Microsoft Azure and, yes, [Oracle Cloud](https://blogs.oracle.com/developers/cloud-foundry-arrives-on-oracle-cloud)! No matter where Cloud Foundry is installed, it's use is basically the same. You authenticate and then tell the platform about your application workload using the `cf` CLI and the `cf push` command. 226 | 227 | .Using `cf` CLI to push the application. 228 | ==== 229 | [source,shell,indent=0] 230 | ---- 231 | cf login -a $CF_API_ENDPOINT -u $CF_USER -s $CF_SPACE -o $CF_ORG 232 | 233 | cf push -p library-service-0.0.1-SNAPSHOT.jar java-magazine-library-service 234 | ---- 235 | ==== 236 | 237 | Once the application is up and running you can access its public HTTP endpoints. You can provision backing services - message queues, databases, caches, etc. - using `cf create-service`. You can scale the application up to multiple load-balanced instances using `cf scale`. You can interrogate the application's metrics, its Spring Boot Actuator endpoints, its health and so much more, all from the Pivotal Apps Manager dashboard. The application is up and running and our clients can talk to it in a secure fashion. 238 | 239 | "Wait, what client?," I hear you saying... 240 | 241 | == A (Reactive) Client 242 | 243 | We've stood up a REST API. We need to connect a client to the service. While we could use the Spring Framework `RestTemplate`, the general workhorse HTTP client that has served us well for the better part of a decade, it's not particularly suited to potentially unlimited streams of data. It expects to be able to convert all response payloads into something by waiting for the end of the response. This isn't going to work if the client is using server sent events, or even just a really large JSON response. Instead, let's use the new Spring WebFlux `WebClient`. 244 | 245 | .Configuring and using an authenticated `WebClient` 246 | ==== 247 | [source,java,indent=0] 248 | ---- 249 | include::./library-client/src/main/java/com/example/libraryclient/LibraryClientApplication.java[] 250 | ---- 251 | ==== 252 | 253 | We configure the `WebClient` and pre-configure a `baseUrl` as well as an `ExchangeFilterFunction` that authenticates the client with the service. The `WebClient` gives us a `Publisher` for the response, which we then print the console. In this case, it doesn't really matter; we've only got four records in our endpoint! The thing that's so valuable is that this code would work even if we were consuming a trillion records. It would work if we were consuming server-sent events that never end. 254 | 255 | == Subscriber#onNext 256 | 257 | What's next? In this article we've looked briefly at building a web service with Spring Boot. We looked at Reactor, Spring Data Kay, Spring Framework 5 and Spring WebFlux, Spring Security 5, and Spring Boot 2.0. Spring Boot 2 makes it easy to assemble the various reactive Spring projects into an application. We didn't look at the Spring Boot Actuator, but it surfaces operational data like metrics, application health and more. It has also been updated to work seamlessly in a reactive world. 258 | 259 | Spring Boot 2 sets the stage for Spring Cloud Finchley. Spring Cloud Finchley builds on Spring Boot 2.0 and updates a number of different APIs, where appropriate, to support reactive programming. Service registration and discovery works in Spring WebFlux based applications. Spring Cloud Commons supports client-side load-balancing across services registered in a service registry (like Apache Zookeepere, Hashicorp Consul and Netflix Eureka) for the Spring Framework `WebClient`. Spring Cloud Netlix Hystrix circuit breakers have always worked naturally with RxJava, which in turn can interop with `Publisher` instances. This is even easier. Spring Cloud Stream supports working with `Publisher` instances to describe how messages arrive and are sent to messaging subsraits like RabbitMQ, Apache Kafka or Redis. Spring Cloud Gateway is a brand new reactive API gateway project that supports HTTP and websocket request proxying, rewriting, load-balancing, circuit breaking, rate limiting and so much more. Spring Cloud Sleuth has been updated to support distributed tracing across reactive boundaries. The list goes on and on. 260 | 261 | The `Future` is a reactive `Publisher`. Begin your journey building production-worthy, agile and reactive applications and services with Spring Boot at the http://start.spring.io[Spring Initializr]. If you have questions, find me on Twitter http://twitter.com/starbuxman[@starbuxman] or mailto://josh@joshlong.com[email (josh@joshlong.com)]. 262 | -------------------------------------------------------------------------------- /README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshlong-attic/reactive-spring-article/52108b224859a3b189e006ebb65ca5ee81d610ba/README.pdf -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./pdf.sh 3 | git commit -am pdf && git push 4 | -------------------------------------------------------------------------------- /library-client/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /library-client/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshlong-attic/reactive-spring-article/52108b224859a3b189e006ebb65ca5ee81d610ba/library-client/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /library-client/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /library-client/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /library-client/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /library-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example 8 | library-client 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | library-client 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.0.0.M6 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 26 | 1.8 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-webflux 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | io.projectreactor 45 | reactor-test 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | 58 | 59 | 60 | 61 | spring-snapshots 62 | Spring Snapshots 63 | https://repo.spring.io/snapshot 64 | 65 | true 66 | 67 | 68 | 69 | spring-milestones 70 | Spring Milestones 71 | https://repo.spring.io/milestone 72 | 73 | false 74 | 75 | 76 | 77 | 78 | 79 | 80 | spring-snapshots 81 | Spring Snapshots 82 | https://repo.spring.io/snapshot 83 | 84 | true 85 | 86 | 87 | 88 | spring-milestones 89 | Spring Milestones 90 | https://repo.spring.io/milestone 91 | 92 | false 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /library-client/src/main/java/com/example/libraryclient/LibraryClientApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryclient; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.ApplicationRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 12 | import org.springframework.web.reactive.function.client.ExchangeFilterFunctions; 13 | import org.springframework.web.reactive.function.client.WebClient; 14 | 15 | @SpringBootApplication 16 | public class LibraryClientApplication { 17 | 18 | @Bean 19 | WebClient client(@Value("${libary-service-url:http://localhost:8080/}") String url) { 20 | ExchangeFilterFunction basicAuth = ExchangeFilterFunctions 21 | .basicAuthentication("rwinch", "pw"); 22 | return WebClient.builder().baseUrl(url).filter(basicAuth).build(); 23 | } 24 | 25 | @Bean 26 | ApplicationRunner run(WebClient client) { 27 | //@formatter:off 28 | return args -> 29 | client 30 | .get() 31 | .uri("/books") 32 | .retrieve() 33 | .bodyToFlux(Book.class) 34 | .subscribe(System.out::println); 35 | //@formatter:on 36 | } 37 | 38 | public static void main(String[] args) { 39 | SpringApplication.run(LibraryClientApplication.class, args); 40 | } 41 | } 42 | 43 | @Data 44 | @AllArgsConstructor 45 | @NoArgsConstructor 46 | class Book { 47 | private String id; 48 | private String title; 49 | private String author; 50 | } 51 | -------------------------------------------------------------------------------- /library-client/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8081 -------------------------------------------------------------------------------- /library-client/src/test/java/com/example/libraryclient/LibraryClientApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryclient; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class LibraryClientApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /library-service/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /library-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshlong-attic/reactive-spring-article/52108b224859a3b189e006ebb65ca5ee81d610ba/library-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /library-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /library-service/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /library-service/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /library-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.example 7 | library-service 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | library-service 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.M6 18 | 19 | 20 | 21 | 22 | true 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 1.1.51 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-actuator 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-mongodb-reactive 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-security 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-webflux 45 | 46 | 47 | org.jetbrains.kotlin 48 | kotlin-stdlib-jre8 49 | ${kotlin.version} 50 | 51 | 52 | org.jetbrains.kotlin 53 | kotlin-reflect 54 | ${kotlin.version} 55 | 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | true 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | io.projectreactor 69 | reactor-test 70 | test 71 | 72 | 73 | org.springframework.security 74 | spring-security-test 75 | test 76 | 77 | 78 | 79 | 80 | ${project.basedir}/src/main/kotlin 81 | 82 | ${project.basedir}/src/test/kotlin 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-maven-plugin 87 | 88 | 89 | kotlin-maven-plugin 90 | org.jetbrains.kotlin 91 | ${kotlin.version} 92 | 93 | 94 | spring 95 | 96 | 1.8 97 | 98 | 99 | 100 | compile 101 | compile 102 | 103 | compile 104 | 105 | 106 | 107 | test-compile 108 | test-compile 109 | 110 | test-compile 111 | 112 | 113 | 114 | 115 | 116 | org.jetbrains.kotlin 117 | kotlin-maven-allopen 118 | ${kotlin.version} 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | spring-snapshots 128 | Spring Snapshots 129 | https://repo.spring.io/snapshot 130 | 131 | true 132 | 133 | 134 | 135 | spring-milestones 136 | Spring Milestones 137 | https://repo.spring.io/milestone 138 | 139 | false 140 | 141 | 142 | 143 | 144 | 145 | 146 | spring-snapshots 147 | Spring Snapshots 148 | https://repo.spring.io/snapshot 149 | 150 | true 151 | 152 | 153 | 154 | spring-milestones 155 | Spring Milestones 156 | https://repo.spring.io/milestone 157 | 158 | false 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/Book.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | @Document 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Book { 14 | @Id 15 | private String id; 16 | private String title; 17 | private String author; 18 | } 19 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/BookRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 4 | import reactor.core.publisher.Flux; 5 | 6 | public interface BookRepository extends ReactiveMongoRepository { 7 | 8 | Flux findByAuthor(String author); 9 | } 10 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/BookRestConfigurationJava.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.web.reactive.function.server.RouterFunction; 7 | 8 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 9 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 10 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 11 | 12 | @Profile("frp-java") 13 | @Configuration 14 | class BookRestConfigurationJava { 15 | 16 | //@formatter:off 17 | @Bean 18 | RouterFunction routes(BookRepository br) { 19 | return 20 | route(GET("/books"), 21 | req -> ok().body(br.findAll(), Book.class)) 22 | .andRoute(GET("/books/{author}"), 23 | req -> ok().body(br.findByAuthor(req.pathVariable("author")), Book.class)); 24 | } 25 | //@formatter:on 26 | } 27 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/BookRestConfigurationKotlin.kt: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice 2 | 3 | import org.springframework.context.annotation.Bean 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.web.reactive.function.server.ServerResponse.ok 7 | import org.springframework.web.reactive.function.server.body 8 | import org.springframework.web.reactive.function.server.router 9 | 10 | @Profile("frp-kotlin") 11 | @Configuration 12 | class BookRestConfigurationKotlin { 13 | 14 | //@formatter:off 15 | @Bean 16 | fun routes(br: BookRepository) = router { 17 | GET("/books") { r -> ok().body(br.findAll()) } 18 | GET("/books/{author}") { r -> ok().body(br.findByAuthor(r.pathVariable("author"))) } 19 | } 20 | //@formatter:on 21 | 22 | } -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/BookRestController.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import reactor.core.publisher.Flux; 8 | 9 | @Profile("mvc-style") 10 | @RestController 11 | class BookRestController { 12 | 13 | private final BookRepository bookRepository; 14 | 15 | BookRestController(BookRepository bookRepository) { 16 | this.bookRepository = bookRepository; 17 | } 18 | 19 | @GetMapping("/books") 20 | Flux all() { 21 | return this.bookRepository.findAll(); 22 | } 23 | 24 | @GetMapping("/books/{author}") 25 | Flux byAuthor(@PathVariable String author) { 26 | return this.bookRepository.findByAuthor(author); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/LibraryServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class LibraryServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | System.setProperty("spring.profiles.active", "security,authorization,frp-java"); 11 | SpringApplication.run(LibraryServiceApplication.class, args); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/SampleBookInitializer.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.ApplicationArguments; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.stereotype.Component; 7 | import reactor.core.publisher.Flux; 8 | 9 | @Slf4j 10 | @Component 11 | class SampleBookInitializer implements ApplicationRunner { 12 | 13 | private final BookRepository bookRepository; 14 | 15 | SampleBookInitializer(BookRepository bookRepository) { 16 | this.bookRepository = bookRepository; 17 | } 18 | 19 | @Override 20 | public void run(ApplicationArguments args) throws Exception { 21 | 22 | // @formatter:off 23 | this.bookRepository 24 | .deleteAll() 25 | .thenMany( 26 | Flux.just("Professional Java Development with the Spring Framework|rjohnson", 27 | "Cloud Native Java|jlong", "Spring Security 3.1|rwinch", "Spring in Action|cwalls")) 28 | .map(t -> t.split("\\|")) 29 | .map(tuple -> new Book(null, tuple[0], tuple[1])) 30 | .flatMap(this.bookRepository::save) 31 | .thenMany(this.bookRepository.findAll()) 32 | .subscribe(book -> log.info(book.toString())); 33 | // @formatter:on 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /library-service/src/main/kotlin/com/example/libraryservice/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.security.authorization.AuthorizationDecision; 7 | import org.springframework.security.authorization.ReactiveAuthorizationManager; 8 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 9 | import org.springframework.security.config.web.server.ServerHttpSecurity; 10 | import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; 11 | import org.springframework.security.core.userdetails.ReactiveUserDetailsService; 12 | import org.springframework.security.core.userdetails.User; 13 | import org.springframework.security.web.server.SecurityWebFilterChain; 14 | import org.springframework.security.web.server.authorization.AuthorizationContext; 15 | 16 | @Profile("security") 17 | @Configuration 18 | @EnableWebFluxSecurity 19 | class SecurityConfiguration { 20 | 21 | @Bean 22 | ReactiveUserDetailsService authentication() { 23 | User.UserBuilder builder = User.withDefaultPasswordEncoder(); 24 | return new MapReactiveUserDetailsService( 25 | builder.username("rjohnson").password("pw").roles("ADMIN").build(), 26 | builder.username("cwalls").password("pw").roles().build(), 27 | builder.username("jlong").password("pw").roles().build(), 28 | builder.username("rwinch").password("pw").roles("ADMIN").build()); 29 | } 30 | 31 | @Bean 32 | @Profile("authorization") 33 | SecurityWebFilterChain authorization(ServerHttpSecurity http) { 34 | ReactiveAuthorizationManager am = (auth, ctx) -> 35 | auth 36 | .map(authentication -> { 37 | Object author = ctx.getVariables().get("author"); 38 | boolean matchesAuthor = authentication.getName().equals(author); 39 | boolean isAdmin = authentication.getAuthorities().stream() 40 | .anyMatch(ga -> ga.getAuthority().contains("ROLE_ADMIN")); 41 | return (matchesAuthor || isAdmin); 42 | }) 43 | .map(AuthorizationDecision::new); 44 | return http 45 | .httpBasic() 46 | .and() 47 | .authorizeExchange() 48 | .pathMatchers("/books/{author}").access(am) 49 | .anyExchange().hasRole("ADMIN") 50 | .and() 51 | .build(); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /library-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | endpoints.default.web.enabled=true -------------------------------------------------------------------------------- /library-service/src/test/kotlin/com/example/libraryservice/LibraryServiceApplicationTests.kt: -------------------------------------------------------------------------------- 1 | package com.example.libraryservice 2 | 3 | import org.junit.Test 4 | import org.junit.runner.RunWith 5 | import org.springframework.boot.test.context.SpringBootTest 6 | import org.springframework.test.context.junit4.SpringRunner 7 | 8 | @RunWith(SpringRunner::class) 9 | @SpringBootTest 10 | class LibraryServiceApplicationTests { 11 | 12 | @Test 13 | fun contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /pdf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | asciidoctor -r asciidoctor-pdf -b pdf README.asciidoc 4 | 5 | 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | com.example 7 | library-root 8 | 0.0.1-SNAPSHOT 9 | pom 10 | library-root 11 | 12 | library-service 13 | library-client 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------