├── .gitignore ├── BOOKER_WITH_DOCKER.md ├── README.md ├── common ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── wildfly │ └── swarm │ └── booker │ └── common │ ├── CORSFilter.java │ ├── ContainerUtils.java │ └── Discoverer.java ├── extra ├── keycloak │ ├── Dockerfile │ ├── README.md │ └── booker.json └── logstash │ ├── README.md │ └── logstash-wildfly.conf ├── library ├── .gitignore ├── docker │ ├── Dockerfile │ └── launch.sh ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── wildfly │ │ └── swarm │ │ └── booker │ │ └── library │ │ ├── LibraryItem.java │ │ ├── LibraryResource.java │ │ ├── Main.java │ │ ├── PersistenceHelper.java │ │ ├── ServicesFactory.java │ │ └── StoreService.java │ └── resources │ ├── META-INF │ └── persistence.xml │ ├── keycloak.json │ └── project-stages.yml ├── pom.xml ├── pricing ├── docker │ ├── Dockerfile │ └── launch.sh ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── wildfly │ │ └── swarm │ │ └── booker │ │ └── pricing │ │ ├── Main.java │ │ └── PricingResource.java │ └── resources │ └── keycloak.json ├── store ├── books.xml ├── docker │ ├── Dockerfile │ └── launch.sh ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── wildfly │ │ │ └── swarm │ │ │ └── booker │ │ │ └── store │ │ │ ├── Book.java │ │ │ ├── Main.java │ │ │ ├── PricingService.java │ │ │ ├── RDFProcessor.java │ │ │ ├── ServicesFactory.java │ │ │ ├── Store.java │ │ │ └── StoreResource.java │ └── resources │ │ ├── META-INF │ │ ├── beans.xml │ │ └── store.xml │ │ ├── WEB-INF │ │ └── web.xml │ │ └── keycloak.json │ └── test │ └── java │ └── ZipkinTest.java ├── vagrant ├── README.md ├── Vagrantfile ├── bin │ ├── run_all.sh │ ├── run_keycloak.sh │ ├── run_kibana.sh │ ├── run_library.sh │ ├── run_logstash.sh │ ├── run_pricing.sh │ ├── run_store.sh │ ├── run_webclient.sh │ └── stop_all.sh └── provisioning │ ├── build.sh │ ├── common.sh │ ├── downloads │ └── InstallConfigRecord.xml │ └── setup.sh └── web-client ├── docker ├── Dockerfile └── launch.sh ├── pom.xml └── src └── main ├── java └── org │ └── wildfly │ └── swarm │ └── booker │ └── Main.java └── resources ├── WEB-INF └── undertow-handlers.conf ├── css └── app.css ├── images ├── diagram.png ├── logo-small.png └── logo.png ├── index.jsp ├── js ├── account.js ├── app.js ├── header.js ├── library.js ├── pagination.js ├── routes.js ├── store.js └── topology.js └── keycloak.jsp /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target/ 4 | build/ 5 | .gradle/ 6 | log 7 | tmp 8 | 9 | # eclipse stuff 10 | .classpath 11 | .project 12 | .settings/ 13 | keycloak*.db 14 | -------------------------------------------------------------------------------- /BOOKER_WITH_DOCKER.md: -------------------------------------------------------------------------------- 1 | 2 | # Running the Booker demo using docker and plain maven 3 | 4 | The following contains brief instructions how to setup the booker demo using docker images for some of the system components. 5 | 6 | ## Prerequisites 7 | 8 | - Docker Toolbox: You'll need the docker toolbox to run containers on your machine: https://www.docker.com/products/docker-toolbox 9 | - Java 8 10 | - Maven 3.3.3 (prior versions had issues with the wildfly-swarm plugin) 11 | 12 | 13 | # Prepare the docker images 14 | 15 | In the following we assume docker is running on `192.168.99.100`. See the 'FAQ' on how to determine the address on your machine. 16 | 17 | ## Consul (http://consul.io) 18 | 19 | With Swarm you have two choices for providing service registration and discovery: jgroups or consul. 20 | In this setup we use consul, because it's much more straightforward to configure and use: 21 | The main interaction with the registry happens through HTTP. 22 | 23 | The progrium consul image lends itself well to launch consul. Simply pull the image: 24 | 25 | ``` 26 | docker pull progrium/consul 27 | ``` 28 | 29 | And start it: 30 | 31 | ``` 32 | docker run -d -p 8400:8400 -p 8500:8500 -p 8600:53/udp -h node1 progrium/consul -server -bootstrap -ui-dir /ui 33 | ``` 34 | 35 | You can verify if it runs successfully by navigating to the consul web UI: 36 | 37 | ``` 38 | http://192.168.99.100:8500 39 | ``` 40 | 41 | ## Keycloak (http://keycloak.jboss.org/) 42 | 43 | Keycloak is used to secure access to the service endpoints. 44 | 45 | Pull the image, but beware of the specific version (`1.7.0.Final`) we use here: 46 | 47 | ``` 48 | docker pull jboss/keycloak:1.7.0.Final 49 | ``` 50 | 51 | And start it: 52 | 53 | ``` 54 | docker run -d -p 9090:8080 jboss/keycloak:1.7.0.Final 55 | ``` 56 | 57 | Once you started keycloak, navigate to the admin interface and import the booker security realm. It can be found at `extra/keycloak/booker.json`: 58 | 59 | ``` 60 | http://192.168.99.100:9090/auth 61 | ``` 62 | 63 | # Prepare the Wildfly Swarm services 64 | 65 | Make sure you've successful build the top level project before you proceed: 66 | 67 | ``` 68 | cd BOOKER_HOME 69 | mvn clean install 70 | ``` 71 | 72 | After this you can build and start each service in it's own JVM. Services are started with a port-poffset (`-Dswarm.port.offset=...`) and require a reference to the Keycloak authentication service (`export BOOKER_KEYCLOAK_SERVICE_HOST=...`) 73 | 74 | ## Build and start each service 75 | 76 | ### Library service 77 | 78 | ``` 79 | cd library 80 | export BOOKER_KEYCLOAK_SERVICE_HOST=192.168.99.100 81 | mvn wildfly-swarm:run -Dswarm.consul.url=http://192.168.99.100:8500/ 82 | ``` 83 | 84 | ### Pricing Service 85 | 86 | ``` 87 | cd library 88 | export BOOKER_KEYCLOAK_SERVICE_HOST=192.168.99.100 89 | mvn wildfly-swarm:run -Dswarm.consul.url=http://192.168.99.100:8500/ -Dswarm.port.offset=150 90 | ``` 91 | 92 | ### Store Service 93 | ``` 94 | cd store 95 | export BOOKER_KEYCLOAK_SERVICE_HOST=192.168.99.100 96 | mvn clean wildfly-swarm:run -Dswarm.consul.url=http://192.168.99.100:8500/ -Dswarm.port.offset=50 97 | ``` 98 | 99 | ### Booker Web Interface 100 | ``` 101 | cd web-client 102 | export BOOKER_KEYCLOAK_SERVICE_HOST=192.168.99.100 103 | mvn clean wildfly-swarm:run -Dswarm.consul.url=http://192.168.99.100:8500/ -Dswarm.port.offset=200 104 | ``` 105 | 106 | ## Verify all services have been registered 107 | 108 | If you head back to the consul web ui (`http://192.168.99.100:8500`) you should see 109 | three backend services registered with consul, exposing to their IP addresses and being in a 'healthy' state. 110 | 111 | You can also query the registry from the command line: 112 | (See also https://www.consul.io/docs/agent/http.html) 113 | 114 | ``` 115 | curl 192.168.99.100:8500/v1/health/node/node1 | pretty.json 116 | [ 117 | { 118 | "CheckID": "service:library:127.0.0.1:8084", 119 | "Name": "Service 'library' check", 120 | "Node": "node1", 121 | "Notes": "", 122 | "Output": "", 123 | "ServiceID": "library:127.0.0.1:8084", 124 | "ServiceName": "library", 125 | "Status": "passing" 126 | }, 127 | { 128 | "CheckID": "service:pricing:127.0.0.1:8230", 129 | "Name": "Service 'pricing' check", 130 | "Node": "node1", 131 | "Notes": "", 132 | "Output": "", 133 | "ServiceID": "pricing:127.0.0.1:8230", 134 | "ServiceName": "pricing", 135 | "Status": "passing" 136 | }, 137 | { 138 | "CheckID": "service:store:127.0.0.1:8130", 139 | "Name": "Service 'store' check", 140 | "Node": "node1", 141 | "Notes": "", 142 | "Output": "", 143 | "ServiceID": "store:127.0.0.1:8130", 144 | "ServiceName": "store", 145 | "Status": "passing" 146 | } 147 | ] 148 | ``` 149 | 150 | 151 | ## Launch the demo store 152 | 153 | Once the above steps have been performed successfully, you are ready to launch the actual web interface. 154 | It leverages the three backend services (library, pricing and store), the service registry and the authentication service. 155 | 156 | ``` 157 | http://localhost:8280 158 | ``` 159 | 160 | > NOTE: Some browsers don't successfully work in this scenario. We've tested with recent chrome versions and seems to work fine. But firefox seems to have problems. 161 | 162 | # FAQ 163 | 164 | ### Wheres my docker machine running? 165 | The default docker toolbox uses `192.168.99.100`, but in some cases the IP may be different. To find out the base IP of the docker machine simply use: 166 | 167 | ``` 168 | docker-machine ip default 169 | 192.168.99.100 170 | ``` 171 | 172 | ### Why does web interface not work? 173 | 174 | Some browser seem to have problems with the current web interface implementation. There is a lot of trickery with same origin restrictions in this setup and some javascript wizardry going on. If you run into problems check the browser javascript log and network communication traces. If in doubt, ping us on IRC. 175 | 176 | ### What's the default Keycloak login? 177 | 178 | At installation time the default login set to `admin:admin`. After your first successful login you are asked to change the credentials. 179 | 180 | ### I get strange SOP errors (Same Origin Policy) 181 | 182 | Did you `export BOOKER_KEYCLOAK_SERVICE_HOST=192.168.99.100`? Maybe try a different browser? 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multicast 2 | 3 | On OSX, you'll probably need to enable multicast on localhost: 4 | 5 | sudo route add -net 230.0.0.0/4 127.0.0.1 6 | 7 | # Components 8 | 9 | ## `web-client` 10 | 11 | Simple, normal `.war` deployment that serves the React.js-based 12 | single-page-app, along with an async servlet to power the 13 | Server-Sent-Events (SSE) for Ribbon service discovery. 14 | 15 | The React.js components communicate directly with the `store` 16 | and `library` services. 17 | 18 | Since this application includes a `main()` method, and creates 19 | a WAR file instead of a JAR file, you'll need to start this 20 | application using the `java -jar` command. 21 | 22 | $ java -jar target/booker-web-client-swarm.jar 23 | 24 | ## `store` 25 | 26 | Book inventory (pulled from Project Gutenberg) served from 27 | a JAX-RS resource from a CDI-injected service. Uses the 28 | `pricing` service via Ribbon to determine the price of each 29 | item in the store. 30 | 31 | Start this using the maven command. 32 | 33 | $ mvn wildfly-swarm:run 34 | 35 | ## `pricing` 36 | 37 | Simple pricing service that indicates everything is $10 if 38 | you're browsing anonymously, or $9 if you're logged in. 39 | 40 | Start this using the maven command. 41 | 42 | $ mvn wildfly-swarm:run 43 | 44 | ## `library` 45 | 46 | Tracks which items are bought by a user using JPA (via an h2 47 | database) from a JAX-RS resource. Communicates with the `store` 48 | service to associate details with a given book ID. 49 | 50 | Start this using the maven command. 51 | 52 | $ mvn wildfly-swarm:run 53 | 54 | ## `keycloak` 55 | 56 | The application is built assuming that there is a running keycloak. 57 | Learn more about keycloak and download it at http://keycloak.jboss.org/ 58 | 59 | Once you have keycloak, start it using the `standalone.sh` command. 60 | 61 | ./bin/standalone.sh -Djboss.http.port=9090 62 | 63 | The default user name and password when you start keycloak for the first 64 | time is admin/admin. Use this to access the keycloak console, and then 65 | select `Add realm`, set the name to `booker` and import the JSON data 66 | from this repository in `extra/keycloak`. 67 | 68 | # `Vagrant` 69 | A Vagrantfile and support scripts to install and run booker in a 70 | virtual machine. Requires Virtualbox and Vagrant be installed. 71 | 72 | # `OpenShift` 73 | 74 | Booker on OpenShift 3.x is still a work in progress, but here are the 75 | steps to try it out. These assume you have a working OpenShift 3 76 | environment already setup and configured. 77 | 78 | First, build the WildFly Swarm source to image container: 79 | 80 | oc new-project booker 81 | oc create -f https://raw.githubusercontent.com/wildfly-swarm/sti-wildflyswarm/master/1.0/wildflyswarm-sti-all.json 82 | 83 | Wait for the WildFly Swarm image to finish building (use `oc status` to 84 | check the progress). Once it has finished, we can start deploying 85 | Booker using that image. 86 | 87 | oc policy add-role-to-user -z default view 88 | oc new-app --name=booker-keycloak --context-dir=extra/keycloak https://github.com/wildfly-swarm/booker 89 | oc expose service booker-keycloak 90 | oc new-app --env="SWARM_JAR=web-client/target/*-swarm.jar" --name=web wildflyswarm-10-centos7~https://github.com/wildfly-swarm/booker 91 | oc expose service web 92 | oc new-app --env="SWARM_JAR=library/target/*-swarm.jar" --name=library wildflyswarm-10-centos7~https://github.com/wildfly-swarm/booker 93 | oc new-app --env="SWARM_JAR=store/target/*-swarm.jar" --name=store wildflyswarm-10-centos7~https://github.com/wildfly-swarm/booker 94 | oc new-app --env="SWARM_JAR=pricing/target/*-swarm.jar" --name=pricing wildflyswarm-10-centos7~https://github.com/wildfly-swarm/booker 95 | 96 | After the `booker-web` application deploys, use `oc get routes` to 97 | find its exposed hostname. Copy and paste that hostname into your 98 | browser to test out the Booker application. 99 | 100 | We can simplify all these steps by creating an OpenShift template that 101 | allows us to create all these resources with a single command. 102 | 103 | 104 | # `Docker` 105 | 106 | Booker uses https://github.com/spotify/docker-maven-plugin to enable docker build for the 4 services. Run with 107 | 108 | mvn clean install docker:build 109 | 110 | to build the docker images, and then run with 111 | 112 | docker run -t -p $hostport:$containerport $dockerimage 113 | 114 | to start the corresponding services. (Don't forget start a single keycloak server before that) 115 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 4.0.0 10 | 11 | 12 | org.wildfly.swarm.booker 13 | booker-parent 14 | 1.0.0.Alpha01-SNAPSHOT 15 | ../ 16 | 17 | 18 | booker-common 19 | 20 | Booker: Common Utils 21 | Booker: Common Utils 22 | 23 | jar 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.wildfly.swarm 31 | jaxrs 32 | 33 | 34 | 35 | com.openshift 36 | openshift-restclient-java 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /common/src/main/java/org/wildfly/swarm/booker/common/CORSFilter.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.common; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.ws.rs.container.ContainerRequestContext; 6 | import javax.ws.rs.container.ContainerResponseContext; 7 | import javax.ws.rs.container.ContainerResponseFilter; 8 | import javax.ws.rs.ext.Provider; 9 | 10 | /** 11 | * @author Bob McWhirter 12 | */ 13 | 14 | @Provider 15 | public class CORSFilter implements ContainerResponseFilter { 16 | 17 | @Override 18 | public void filter(final ContainerRequestContext requestContext, 19 | final ContainerResponseContext cres) throws IOException { 20 | cres.getHeaders().add("Access-Control-Allow-Origin", "*"); 21 | cres.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); 22 | cres.getHeaders().add("Access-Control-Allow-Credentials", "true"); 23 | cres.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); 24 | cres.getHeaders().add("Access-Control-Max-Age", "1209600"); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/org/wildfly/swarm/booker/common/ContainerUtils.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.common; 2 | 3 | import org.jboss.shrinkwrap.api.Archive; 4 | import org.jboss.shrinkwrap.api.Node; 5 | import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; 6 | import org.wildfly.swarm.config.logging.Level; 7 | import org.wildfly.swarm.config.logging.Logger; 8 | import org.wildfly.swarm.logging.LoggingFraction; 9 | import org.wildfly.swarm.logging.LoggingProperties; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | 16 | public class ContainerUtils { 17 | 18 | public static LoggingFraction loggingFraction() { 19 | String prop = System.getProperty(LoggingProperties.LOGGING); 20 | LoggingFraction fraction = null; 21 | if (prop != null) { 22 | prop = prop.trim().toLowerCase(); 23 | 24 | if (prop.equals("debug")) { 25 | fraction = LoggingFraction.createDebugLoggingFraction(); 26 | } else if (prop.equals("trace")) { 27 | fraction = LoggingFraction.createTraceLoggingFraction(); 28 | } 29 | } 30 | if (fraction == null) { 31 | fraction = LoggingFraction.createDefaultLoggingFraction(); 32 | } 33 | 34 | fraction.logger(new Logger("org.openshift.ping") 35 | .level(Level.TRACE)); 36 | fraction.logger(new Logger("org.jgroups.protocols.openshift") 37 | .level(Level.TRACE)); 38 | 39 | return fraction; 40 | } 41 | 42 | public static void addExternalKeycloakJson(Archive archive) { 43 | String keycloakPath = "WEB-INF/keycloak.json"; 44 | Node keycloakJson = archive.get(keycloakPath); 45 | if (keycloakJson == null) { 46 | return; 47 | } 48 | InputStream inputStream = keycloakJson.getAsset().openStream(); 49 | StringBuilder str = new StringBuilder(); 50 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { 51 | String externalKeycloakUrl = Discoverer.externalKeycloakUrl(80); 52 | String line; 53 | while ((line = reader.readLine()) != null) { 54 | line = line.replace("http://localhost:9090", externalKeycloakUrl); 55 | str.append(line).append("\n"); 56 | } 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | } 60 | archive.add(new ByteArrayAsset(str.toString().getBytes()), keycloakPath); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /common/src/main/java/org/wildfly/swarm/booker/common/Discoverer.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.common; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | import java.util.List; 7 | 8 | import com.openshift.restclient.ClientBuilder; 9 | import com.openshift.restclient.IClient; 10 | import com.openshift.restclient.ResourceKind; 11 | import com.openshift.restclient.model.IPod; 12 | import com.openshift.restclient.model.IProject; 13 | import com.openshift.restclient.model.IService; 14 | import com.openshift.restclient.model.route.IRoute; 15 | import org.wildfly.swarm.spi.api.Environment; 16 | 17 | public class Discoverer { 18 | 19 | private static final boolean IN_OPENSHIFT = Environment.openshift(); 20 | 21 | public static String serviceHost(String serviceName, String defaultHost) { 22 | String host = serviceHostFromEnv(serviceName); 23 | 24 | if (host == null) { 25 | try { 26 | host = serviceHostFromOpenshift(serviceName); 27 | } catch (IOException ex) { 28 | System.err.println("Error discovering service host via OpenShift:"); 29 | ex.printStackTrace(); 30 | } 31 | } 32 | 33 | if (host == null) { 34 | host = defaultHost; 35 | } 36 | 37 | return host; 38 | } 39 | 40 | public static int servicePort(String serviceName, int defaultPort) { 41 | int port = servicePortFromEnv(serviceName); 42 | 43 | if (port < 0) { 44 | try { 45 | port = servicePortFromOpenshift(serviceName); 46 | } catch (IOException ex) { 47 | System.err.println("Error discovering service port via OpenShift:"); 48 | ex.printStackTrace(); 49 | } 50 | } 51 | 52 | if (port < 0) { 53 | port = defaultPort; 54 | } 55 | 56 | return port; 57 | } 58 | 59 | public static String serviceHostFromEnv(String serviceName) { 60 | String envName = serviceName.replace("-", "_").toUpperCase() + "_SERVICE_HOST"; 61 | return System.getenv(envName); 62 | } 63 | 64 | public static int servicePortFromEnv(String serviceName) { 65 | String envName = serviceName.replace("-", "_").toUpperCase() + "_SERVICE_PORT"; 66 | String envPort = System.getenv(envName); 67 | if (envPort == null) { 68 | return -1; 69 | } 70 | 71 | return Integer.parseInt(envPort); 72 | } 73 | 74 | public static String serviceHostFromOpenshift(String serviceName) throws IOException { 75 | if (!IN_OPENSHIFT) { 76 | return null; 77 | } 78 | IClient client = openshiftClient(); 79 | List projects = client.list(ResourceKind.PROJECT); 80 | for (IProject project : projects) { 81 | List services = client.list(ResourceKind.SERVICE, project.getName()); 82 | for (IService service : services) { 83 | if (service.getName().equals(serviceName)) { 84 | return service.getPortalIP(); 85 | } 86 | } 87 | } 88 | return null; 89 | } 90 | 91 | public static int servicePortFromOpenshift(String serviceName) throws IOException { 92 | if (!IN_OPENSHIFT) { 93 | return -1; 94 | } 95 | IClient client = openshiftClient(); 96 | List projects = client.list(ResourceKind.PROJECT); 97 | for (IProject project : projects) { 98 | List services = client.list(ResourceKind.SERVICE, project.getName()); 99 | for (IService service : services) { 100 | if (service.getName().equals(serviceName)) { 101 | return service.getPort(); 102 | } 103 | } 104 | } 105 | return -1; 106 | } 107 | 108 | public static String serviceHostToExternalHost(String serviceHost) throws IOException { 109 | if (!IN_OPENSHIFT) { 110 | return serviceHost; 111 | } 112 | IClient client = openshiftClient(); 113 | List projects = client.list(ResourceKind.PROJECT); 114 | for (IProject project : projects) { 115 | // We translate serviceHost to pod to make sure we're getting 116 | // the route from the correct project 117 | List pods = client.list(ResourceKind.POD, project.getName()); 118 | String matchingServiceName = null; 119 | for (IPod pod : pods) { 120 | if (pod.getIP().equals(serviceHost) || pod.getHost().equals(serviceHost)) { 121 | matchingServiceName = pod.getLabels().get("app"); 122 | break; 123 | } 124 | } 125 | if (matchingServiceName == null) { 126 | List services = client.list(ResourceKind.SERVICE, project.getName()); 127 | for (IService service : services) { 128 | if (service.getPortalIP().equals(serviceHost)) { 129 | matchingServiceName = service.getName(); 130 | } 131 | } 132 | } 133 | if (matchingServiceName == null) { 134 | continue; 135 | } 136 | List routes = client.list(ResourceKind.ROUTE, project.getName()); 137 | for (IRoute route : routes) { 138 | if (route.getServiceName().equals(matchingServiceName)) { 139 | return route.getHost(); 140 | } 141 | } 142 | } 143 | return serviceHost; 144 | 145 | } 146 | 147 | public static String externalKeycloakUrl(int externalHttpPort) throws IOException { 148 | String keycloakHost = serviceHost("booker-keycloak", "localhost"); 149 | int keycloakPort = Discoverer.servicePort("booker-keycloak", 9090); 150 | 151 | String externalKeycloakHost = serviceHostToExternalHost(keycloakHost); 152 | if (!externalKeycloakHost.equals(keycloakHost)) { 153 | keycloakHost = externalKeycloakHost; 154 | keycloakPort = externalHttpPort; 155 | } 156 | 157 | String url = "http://" + keycloakHost; 158 | if (keycloakPort != 80) { 159 | url += ":" + keycloakPort; 160 | } 161 | return url; 162 | } 163 | 164 | protected static IClient openshiftClient() throws IOException { 165 | String kubeHost = serviceHostFromEnv("kubernetes"); 166 | int kubePort = servicePortFromEnv("kubernetes"); 167 | 168 | String tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"; 169 | String token = new String(Files.readAllBytes(Paths.get(tokenFile))); 170 | 171 | IClient client = new ClientBuilder("https://" + kubeHost + ":" + kubePort).build(); 172 | client.getAuthorizationContext().setToken(token); 173 | return client; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /extra/keycloak/Dockerfile: -------------------------------------------------------------------------------- 1 | # This image provides a customized version of Keycloak with the booker 2 | # realm configured out of the box. 3 | 4 | FROM jboss/keycloak:2.1.0.Final 5 | 6 | MAINTAINER Ben Browning 7 | 8 | EXPOSE 8080 9 | 10 | COPY ./booker.json /tmp/booker.json 11 | 12 | RUN chmod -R go+rw $JBOSS_HOME/standalone 13 | 14 | CMD ["-b", "0.0.0.0", "-Dkeycloak.migration.action=import", "-Dkeycloak.migration.provider=singleFile", "-Dkeycloak.migration.strategy=OVERWRITE_EXISTING", "-Dkeycloak.migration.file=/tmp/booker.json"] 15 | -------------------------------------------------------------------------------- /extra/keycloak/README.md: -------------------------------------------------------------------------------- 1 | # Download Keycloak 2 | 3 | http://downloads.jboss.org/keycloak/2.1.0.Final/keycloak-2.1.0.Final.zip 4 | 5 | # Launch 6 | 7 | Launch Keycloak on port 9090 and import `booker.json` 8 | 9 | ./bin/standalone.sh -Djboss.http.port=9090 10 | 11 | # Import realm details 12 | 13 | Using the web UI, import `booker.json` 14 | 15 | The realm will be created, and a user named 'bob' with the password 16 | of 'bob' will be avilable. 17 | -------------------------------------------------------------------------------- /extra/keycloak/booker.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "1236f63b-6f1e-48f9-a068-7056000866e4", 3 | "realm" : "booker", 4 | "notBefore" : 0, 5 | "accessTokenLifespan" : 300, 6 | "ssoSessionIdleTimeout" : 36000, 7 | "ssoSessionMaxLifespan" : 36000, 8 | "accessCodeLifespan" : 60, 9 | "accessCodeLifespanUserAction" : 300, 10 | "accessCodeLifespanLogin" : 1800, 11 | "enabled" : true, 12 | "sslRequired" : "external", 13 | "registrationAllowed" : true, 14 | "registrationEmailAsUsername" : true, 15 | "rememberMe" : true, 16 | "verifyEmail" : false, 17 | "resetPasswordAllowed" : false, 18 | "editUsernameAllowed" : false, 19 | "bruteForceProtected" : false, 20 | "maxFailureWaitSeconds" : 900, 21 | "minimumQuickLoginWaitSeconds" : 60, 22 | "waitIncrementSeconds" : 60, 23 | "quickLoginCheckMilliSeconds" : 1000, 24 | "maxDeltaTimeSeconds" : 43200, 25 | "failureFactor" : 30, 26 | "privateKey" : "MIIEowIBAAKCAQEAm/yXPhm3xlNsYOLQGM4YxqjAe5mjBxKcxJCYyQGzz36DQFO59IclAxaFEsgN3OorVL2W9wLLGUveoV47s8rOdq4+obHiO2C/bgDSVcvg+X8RRAngZDR04iPPdD+cjMdxAYb/WsGjlOKju+U8Pk2o/TnRHNmZgbwE9JDnhGrmFycCgu7oQGk6KDVpsp3zVIsr2qrah0ujBwUbPti8NN4OZBupMzgR3oOjzJ9dhkh9qaQN6SErnRj3lENAh7rbizKEWnIxImgi6m6ogLNWMxNKJlrRbER2LCSegDXhhO3zuBhHb0xZ1P+dO+4LL478SCQStutrGSoO0Yc0GiEVFBIncQIDAQABAoIBAEi0tH4ymJEtgz+pmJtnPO8j6wvxcaazSzTnvP879ubJLj5GiE9sypik34QAkU2eLHJEKGCcAbSZw3xtouBLsG5rCzBs7GkG0FNiuKda71L7oj2XaOrr9GsPi/vARqTrtUWj+dQAm9GizfSfhbP4mMLnNwv+dOmejKS+AdubxAGX5Slp4oPDLItxMNVuObZBaCO+8XMFfT4zkQVx/Y4E1Sd5eX9O54f0t1V831eIyttlAfwYIl+PzLzWmFc/yqEmkXkb14SQPP3nwDYIodmfmqf4MEB+f885f0i3HRpNwMt2d1YIrkRv7CGnYi9ep8bri2Ji8s3cIWVM7M+umin0XS0CgYEA7WoetUcGhr8cYKoWojVvWCve/vRt4b261T5np3vy+08xwBgR5F2XAxyaOXpoAQbsCKna6652imE+bMVTmXq3TA5gb4+uJmvNOHdmW/+S5gHlhHQ6h7EV4rQgbCRNHqYvY2v6OHtRLkM5dtMMfX+352X3+G4sljADI4p1Mz4+qCcCgYEAqDKf+miW4kClrvIgfrdIBcFTMXdNnbjPuR3XLCvFKNoLKiyegbiAfcXIV2yLkE2beSYmChkjmfzlBYY2mgssv2+VWa9KFbgFyyJtRLxGvTsjILJ9qgObeM9S4YQr2QHKsoNs6XOCMj/vgR8qZdzeSu2a+0FhFqJALE/7/At9mqcCgYApx4wB4K7gVY9b33K+NuMSRB4pBcC0ghv33NSG38Qhv40EvfegFWw2yvjE9qqIy7wLxkBVS01nCrfeklck4jiRdNI5r7I45q2lqzVjKTWjrl/CkGWgbWRpkcNG5JiBtUMPnvfYqrLdjkz1gSxohWpKmWIq4NCN2YMj5QC89M437wKBgQCTEsFkmvPknxbBZSMY6nbl4Wg3htStp56rhfZSv2tFD3RXynBHieeBEuLd3yIialoak0sL5XOGAObZ78Pq6v6T3qQ/qdEkMKeQOmhFhERuzzkmLC1J7qGR/+e/Hm2g8bomYllnwQMFoXZx96iBLaKK7j1omoCk4/ctSDtU509LXQKBgG6gJ97WIg8f7kpxb0QuRUpkc59UfEI42v31ini/OyTVsu8gKKfJPxaa02LicSbKaId3ICoLTChkZ4XAppuj9n97UR7aCG3Wd55mCBWUvZk8VknaaCqfRdpsVgmOj+uRi0pyH9u2iarzDZSjYTYMcrcQ7wpZ33HXu5xETdt8X19A", 27 | "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm/yXPhm3xlNsYOLQGM4YxqjAe5mjBxKcxJCYyQGzz36DQFO59IclAxaFEsgN3OorVL2W9wLLGUveoV47s8rOdq4+obHiO2C/bgDSVcvg+X8RRAngZDR04iPPdD+cjMdxAYb/WsGjlOKju+U8Pk2o/TnRHNmZgbwE9JDnhGrmFycCgu7oQGk6KDVpsp3zVIsr2qrah0ujBwUbPti8NN4OZBupMzgR3oOjzJ9dhkh9qaQN6SErnRj3lENAh7rbizKEWnIxImgi6m6ogLNWMxNKJlrRbER2LCSegDXhhO3zuBhHb0xZ1P+dO+4LL478SCQStutrGSoO0Yc0GiEVFBIncQIDAQAB", 28 | "certificate" : "MIICmzCCAYMCBgFO8+B7PTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZib29rZXIwHhcNMTUwODAzMTQwMjMxWhcNMjUwODAzMTQwNDExWjARMQ8wDQYDVQQDDAZib29rZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb/Jc+GbfGU2xg4tAYzhjGqMB7maMHEpzEkJjJAbPPfoNAU7n0hyUDFoUSyA3c6itUvZb3AssZS96hXjuzys52rj6hseI7YL9uANJVy+D5fxFECeBkNHTiI890P5yMx3EBhv9awaOU4qO75Tw+Taj9OdEc2ZmBvAT0kOeEauYXJwKC7uhAaTooNWmynfNUiyvaqtqHS6MHBRs+2Lw03g5kG6kzOBHeg6PMn12GSH2ppA3pISudGPeUQ0CHutuLMoRacjEiaCLqbqiAs1YzE0omWtFsRHYsJJ6ANeGE7fO4GEdvTFnU/5077gsvjvxIJBK262sZKg7RhzQaIRUUEidxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIpI8otYSL5Tjsl6R9AnyWZrL4y+AE58DrwM6FDdjhqybr6XHlBX2v8mKWGgkuD4ZVlmrz840vWu5r23QA3gH5XDaYXDhwtv1yT0NaA5IDqgaaxpGk1pxW4+tHzKgSTMC0ujy/qZzqzOpc/qKUinaSXktD9F3k4Ch49IV6QIm4F2K5YLejbsTJ2Aq6MwOTa3n5xsStjlZVzf9G4/neG/JhwPZyeWZb+RAS0B4Q4TpY0G7y2hncB5xaUIuLI3I0kvG7CuAYEMrQqt8tvliDng2qE+hshyw5wDSfcCvpfFM495NQPo12VVmUtpjQzlzyCWNQds8sTGLXElgBhM83NVpJM=", 29 | "codeSecret" : "d55a4357-131a-4da2-b8c7-47797a2898d5", 30 | "roles" : { 31 | "realm" : [ { 32 | "id" : "1a1bd18b-e9bd-4d47-a147-af27ae22e85c", 33 | "name" : "admin", 34 | "composite" : false 35 | } ], 36 | "client" : { 37 | "realm-management" : [ { 38 | "id" : "ee1fdb08-cafa-487b-b446-9500e9e334a9", 39 | "name" : "realm-admin", 40 | "description" : "${role_realm-admin}", 41 | "composite" : true, 42 | "composites" : { 43 | "client" : { 44 | "realm-management" : [ "view-users", "manage-users", "view-realm", "impersonation", "view-clients", "manage-identity-providers", "view-events", "manage-clients", "manage-events", "manage-realm", "view-identity-providers" ] 45 | } 46 | } 47 | }, { 48 | "id" : "75f9ef54-82eb-4ecb-be4f-58e2ddf81eff", 49 | "name" : "view-users", 50 | "description" : "${role_view-users}", 51 | "composite" : false 52 | }, { 53 | "id" : "6f859730-3972-48e9-a4c7-a9ee29561a23", 54 | "name" : "manage-users", 55 | "description" : "${role_manage-users}", 56 | "composite" : false 57 | }, { 58 | "id" : "c5c0d13f-5b44-42da-a8b8-84ff54fc629c", 59 | "name" : "view-realm", 60 | "description" : "${role_view-realm}", 61 | "composite" : false 62 | }, { 63 | "id" : "cd55c200-6c39-462d-9494-21c4c866187f", 64 | "name" : "impersonation", 65 | "description" : "${role_impersonation}", 66 | "composite" : false 67 | }, { 68 | "id" : "69fd2797-7216-4fb0-b4bd-321555fba3ae", 69 | "name" : "manage-identity-providers", 70 | "description" : "${role_manage-identity-providers}", 71 | "composite" : false 72 | }, { 73 | "id" : "cd9b93a9-1a26-48e5-8084-e8b3e24d78aa", 74 | "name" : "view-clients", 75 | "description" : "${role_view-clients}", 76 | "composite" : false 77 | }, { 78 | "id" : "fc4cf4e4-d659-4f90-a2b0-c634ef5987e8", 79 | "name" : "view-events", 80 | "description" : "${role_view-events}", 81 | "composite" : false 82 | }, { 83 | "id" : "570cf493-4bde-480f-8d0c-2ed1cd52d982", 84 | "name" : "manage-clients", 85 | "description" : "${role_manage-clients}", 86 | "composite" : false 87 | }, { 88 | "id" : "6aaed148-35c9-446e-a68a-dc9562f2f1ce", 89 | "name" : "manage-events", 90 | "description" : "${role_manage-events}", 91 | "composite" : false 92 | }, { 93 | "id" : "28c044f7-fe6b-4f7b-8280-3be00fcad5e6", 94 | "name" : "manage-realm", 95 | "description" : "${role_manage-realm}", 96 | "composite" : false 97 | }, { 98 | "id" : "1f406d8f-a9d6-4273-ba34-2876d55b4efa", 99 | "name" : "view-identity-providers", 100 | "description" : "${role_view-identity-providers}", 101 | "composite" : false 102 | } ], 103 | "web-client" : [ ], 104 | "security-admin-console" : [ ], 105 | "broker" : [ { 106 | "id" : "1319953c-0cff-4d80-8778-b4759214ee55", 107 | "name" : "read-token", 108 | "description" : "${role_read-token}", 109 | "composite" : false 110 | } ], 111 | "account" : [ { 112 | "id" : "635ba068-50b5-4446-9f9f-767113d58bc5", 113 | "name" : "view-profile", 114 | "description" : "${role_view-profile}", 115 | "composite" : false 116 | }, { 117 | "id" : "07659c28-b197-4675-af49-c8b2af39a1f7", 118 | "name" : "manage-account", 119 | "description" : "${role_manage-account}", 120 | "composite" : false 121 | } ] 122 | } 123 | }, 124 | "requiredCredentials" : [ "password" ], 125 | "users" : [ { 126 | "id" : "613edeb2-82fd-4e4b-ba28-6c1bf3a3690e", 127 | "createdTimestamp" : 1438625780456, 128 | "username" : "bob", 129 | "enabled" : true, 130 | "totp" : false, 131 | "emailVerified" : false, 132 | "firstName" : "Bob", 133 | "lastName" : "McWhirter", 134 | "email" : "bob@mcwhirter.org", 135 | "credentials" : [ { 136 | "type" : "password", 137 | "hashedSaltedValue" : "uFQrDULHzkZ9UjAT8e1eJFsCZzarydM4FB5BCYTjiZQhrP1wkxLz5jHWMncs+kV2niiR/Dl+RVwyB8MF3uQ6+A==", 138 | "salt" : "E60TQ4q5Gw1vlG/CzJyxCA==", 139 | "hashIterations" : 1, 140 | "temporary" : false 141 | } ], 142 | "requiredActions" : [ ], 143 | "clientRoles" : { 144 | "account" : [ "view-profile", "manage-account" ] 145 | } 146 | }, { 147 | "id" : "03e88eb9-c2ad-46ca-a8c4-0bce2ef392e0", 148 | "createdTimestamp" : 1442863606554, 149 | "username" : "jane", 150 | "enabled" : true, 151 | "totp" : false, 152 | "emailVerified" : true, 153 | "firstName" : "Jane", 154 | "lastName" : "Doe", 155 | "email" : "jane@doe.com", 156 | "credentials" : [ { 157 | "type" : "password", 158 | "hashedSaltedValue" : "U44OoP4tdxK1INJAgC2d9RLGmrdjTVgpHDU09aozbLgMZHtZlFrIbIxr05i7seslp2CMAydZYXGjlpCoTgxNdA==", 159 | "salt" : "lklrnkvIBsKTILi5XQBRmw==", 160 | "hashIterations" : 1, 161 | "temporary" : false 162 | } ], 163 | "requiredActions" : [ ], 164 | "clientRoles" : { 165 | "account" : [ "view-profile", "manage-account" ] 166 | } 167 | }, { 168 | "id" : "e6972b83-00a0-42dd-a74d-13ec7fdf3086", 169 | "createdTimestamp" : 1442863591809, 170 | "username" : "john", 171 | "enabled" : true, 172 | "totp" : false, 173 | "emailVerified" : true, 174 | "firstName" : "John", 175 | "lastName" : "Doe", 176 | "email" : "john@doe.com", 177 | "credentials" : [ { 178 | "type" : "password", 179 | "hashedSaltedValue" : "lJjREXdMdYIJWjg02Cyf47Hct1rLD7cG7Mq9AmVbpXYiVLx8LXdVjGVRMf8KhOCvq0dA2bvaI+dRnCxRq7MMUw==", 180 | "salt" : "m+Vn09HDhsoG3GGqhanq1g==", 181 | "hashIterations" : 1, 182 | "temporary" : false 183 | } ], 184 | "requiredActions" : [ ], 185 | "clientRoles" : { 186 | "account" : [ "view-profile", "manage-account" ] 187 | } 188 | } ], 189 | "clientScopeMappings" : { 190 | "realm-management" : [ { 191 | "client" : "security-admin-console", 192 | "roles" : [ "realm-admin" ] 193 | } ] 194 | }, 195 | "clients" : [ { 196 | "id" : "15502431-3e09-4008-8339-8919878d4098", 197 | "clientId" : "realm-management", 198 | "name" : "${client_realm-management}", 199 | "surrogateAuthRequired" : false, 200 | "enabled" : true, 201 | "secret" : "e8d0298b-47be-456a-a3e9-5320d66aeaff", 202 | "redirectUris" : [ ], 203 | "webOrigins" : [ ], 204 | "notBefore" : 0, 205 | "bearerOnly" : true, 206 | "consentRequired" : false, 207 | "serviceAccountsEnabled" : false, 208 | "directGrantsOnly" : false, 209 | "publicClient" : false, 210 | "frontchannelLogout" : false, 211 | "attributes" : { }, 212 | "fullScopeAllowed" : false, 213 | "nodeReRegistrationTimeout" : 0, 214 | "protocolMappers" : [ { 215 | "id" : "872f6487-5d19-42de-be39-8cf0f71f1d5f", 216 | "name" : "email", 217 | "protocol" : "openid-connect", 218 | "protocolMapper" : "oidc-usermodel-property-mapper", 219 | "consentRequired" : true, 220 | "consentText" : "${email}", 221 | "config" : { 222 | "user.attribute" : "email", 223 | "id.token.claim" : "true", 224 | "access.token.claim" : "true", 225 | "claim.name" : "email", 226 | "Claim JSON Type" : "String" 227 | } 228 | }, { 229 | "id" : "f2f25a24-83d0-4387-974a-a3935d6b95e8", 230 | "name" : "role list", 231 | "protocol" : "saml", 232 | "protocolMapper" : "saml-role-list-mapper", 233 | "consentRequired" : false, 234 | "config" : { 235 | "single" : "false", 236 | "attribute.nameformat" : "Basic", 237 | "attribute.name" : "Role" 238 | } 239 | }, { 240 | "id" : "91c759c1-a3b5-4bdb-9d8e-4a2e508028e2", 241 | "name" : "family name", 242 | "protocol" : "openid-connect", 243 | "protocolMapper" : "oidc-usermodel-property-mapper", 244 | "consentRequired" : true, 245 | "consentText" : "${familyName}", 246 | "config" : { 247 | "user.attribute" : "lastName", 248 | "id.token.claim" : "true", 249 | "access.token.claim" : "true", 250 | "claim.name" : "family_name", 251 | "Claim JSON Type" : "String" 252 | } 253 | }, { 254 | "id" : "2325e23f-accd-4b00-8a05-7add5501d72e", 255 | "name" : "given name", 256 | "protocol" : "openid-connect", 257 | "protocolMapper" : "oidc-usermodel-property-mapper", 258 | "consentRequired" : true, 259 | "consentText" : "${givenName}", 260 | "config" : { 261 | "user.attribute" : "firstName", 262 | "id.token.claim" : "true", 263 | "access.token.claim" : "true", 264 | "claim.name" : "given_name", 265 | "Claim JSON Type" : "String" 266 | } 267 | }, { 268 | "id" : "15de170b-7bfd-47ed-a090-f7f1ce3b7f70", 269 | "name" : "username", 270 | "protocol" : "openid-connect", 271 | "protocolMapper" : "oidc-usermodel-property-mapper", 272 | "consentRequired" : true, 273 | "consentText" : "${username}", 274 | "config" : { 275 | "user.attribute" : "username", 276 | "id.token.claim" : "true", 277 | "access.token.claim" : "true", 278 | "claim.name" : "preferred_username", 279 | "Claim JSON Type" : "String" 280 | } 281 | }, { 282 | "id" : "4ffd62f1-8c3e-4ca8-b942-29e94e03813f", 283 | "name" : "full name", 284 | "protocol" : "openid-connect", 285 | "protocolMapper" : "oidc-full-name-mapper", 286 | "consentRequired" : true, 287 | "consentText" : "${fullName}", 288 | "config" : { 289 | "id.token.claim" : "true", 290 | "access.token.claim" : "true" 291 | } 292 | } ] 293 | }, { 294 | "id" : "b7f2db06-85b7-4972-8e9d-4d54b6b7a6b1", 295 | "clientId" : "web-client", 296 | "name" : "Web Client", 297 | "baseUrl" : "http://localhost:8080/", 298 | "surrogateAuthRequired" : false, 299 | "enabled" : true, 300 | "secret" : "deec63e4-d242-4180-b402-80fba0a9187e", 301 | "redirectUris" : [ "*" ], 302 | "webOrigins" : [ "*" ], 303 | "notBefore" : 0, 304 | "bearerOnly" : false, 305 | "consentRequired" : false, 306 | "serviceAccountsEnabled" : false, 307 | "directGrantsOnly" : false, 308 | "publicClient" : false, 309 | "frontchannelLogout" : false, 310 | "protocol" : "openid-connect", 311 | "attributes" : { 312 | "saml.assertion.signature" : "false", 313 | "saml.force.post.binding" : "false", 314 | "saml.multivalued.roles" : "false", 315 | "saml.signature.algorithm" : "RSA_SHA256", 316 | "saml.encrypt" : "false", 317 | "saml_force_name_id_format" : "false", 318 | "saml.client.signature" : "false", 319 | "saml.authnstatement" : "true", 320 | "saml_name_id_format" : "username", 321 | "saml.server.signature" : "false", 322 | "saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#" 323 | }, 324 | "fullScopeAllowed" : true, 325 | "nodeReRegistrationTimeout" : -1, 326 | "protocolMappers" : [ { 327 | "id" : "8a8584db-11d9-42b8-85b2-d080e6e51c31", 328 | "name" : "email", 329 | "protocol" : "openid-connect", 330 | "protocolMapper" : "oidc-usermodel-property-mapper", 331 | "consentRequired" : true, 332 | "consentText" : "${email}", 333 | "config" : { 334 | "user.attribute" : "email", 335 | "id.token.claim" : "true", 336 | "access.token.claim" : "true", 337 | "claim.name" : "email", 338 | "Claim JSON Type" : "String" 339 | } 340 | }, { 341 | "id" : "f7277609-dc35-40ad-932f-9fe2aaf239c5", 342 | "name" : "family name", 343 | "protocol" : "openid-connect", 344 | "protocolMapper" : "oidc-usermodel-property-mapper", 345 | "consentRequired" : true, 346 | "consentText" : "${familyName}", 347 | "config" : { 348 | "user.attribute" : "lastName", 349 | "id.token.claim" : "true", 350 | "access.token.claim" : "true", 351 | "claim.name" : "family_name", 352 | "Claim JSON Type" : "String" 353 | } 354 | }, { 355 | "id" : "32be5146-b8f0-4c0a-80a6-0fb53b5b21fb", 356 | "name" : "username", 357 | "protocol" : "openid-connect", 358 | "protocolMapper" : "oidc-usermodel-property-mapper", 359 | "consentRequired" : true, 360 | "consentText" : "${username}", 361 | "config" : { 362 | "user.attribute" : "username", 363 | "id.token.claim" : "true", 364 | "access.token.claim" : "true", 365 | "claim.name" : "preferred_username", 366 | "Claim JSON Type" : "String" 367 | } 368 | }, { 369 | "id" : "696f2f40-8655-439b-918d-a2db1a45d334", 370 | "name" : "full name", 371 | "protocol" : "openid-connect", 372 | "protocolMapper" : "oidc-full-name-mapper", 373 | "consentRequired" : true, 374 | "consentText" : "${fullName}", 375 | "config" : { 376 | "id.token.claim" : "true", 377 | "access.token.claim" : "true" 378 | } 379 | }, { 380 | "id" : "8e6eeba9-eb5a-4e65-8931-61e5a8b4eaf1", 381 | "name" : "given name", 382 | "protocol" : "openid-connect", 383 | "protocolMapper" : "oidc-usermodel-property-mapper", 384 | "consentRequired" : true, 385 | "consentText" : "${givenName}", 386 | "config" : { 387 | "user.attribute" : "firstName", 388 | "id.token.claim" : "true", 389 | "access.token.claim" : "true", 390 | "claim.name" : "given_name", 391 | "Claim JSON Type" : "String" 392 | } 393 | }, { 394 | "id" : "859dcbb0-bb84-4211-b5a4-cc36549f369e", 395 | "name" : "role list", 396 | "protocol" : "saml", 397 | "protocolMapper" : "saml-role-list-mapper", 398 | "consentRequired" : false, 399 | "config" : { 400 | "single" : "false", 401 | "attribute.nameformat" : "Basic", 402 | "attribute.name" : "Role" 403 | } 404 | } ] 405 | }, { 406 | "id" : "531311b1-d433-404f-a7f1-bf7a1b23e4f8", 407 | "clientId" : "security-admin-console", 408 | "name" : "${client_security-admin-console}", 409 | "baseUrl" : "/auth/admin/booker/console/index.html", 410 | "surrogateAuthRequired" : false, 411 | "enabled" : true, 412 | "secret" : "f1411a2a-bd06-430f-b5a8-bf969e42b1ee", 413 | "redirectUris" : [ "/auth/admin/booker/console/*" ], 414 | "webOrigins" : [ ], 415 | "notBefore" : 0, 416 | "bearerOnly" : false, 417 | "consentRequired" : false, 418 | "serviceAccountsEnabled" : false, 419 | "directGrantsOnly" : false, 420 | "publicClient" : true, 421 | "frontchannelLogout" : false, 422 | "attributes" : { }, 423 | "fullScopeAllowed" : false, 424 | "nodeReRegistrationTimeout" : 0, 425 | "protocolMappers" : [ { 426 | "id" : "d960b128-6bfe-4243-bf39-6743079a242e", 427 | "name" : "email", 428 | "protocol" : "openid-connect", 429 | "protocolMapper" : "oidc-usermodel-property-mapper", 430 | "consentRequired" : true, 431 | "consentText" : "${email}", 432 | "config" : { 433 | "user.attribute" : "email", 434 | "id.token.claim" : "true", 435 | "access.token.claim" : "true", 436 | "claim.name" : "email", 437 | "Claim JSON Type" : "String" 438 | } 439 | }, { 440 | "id" : "d2ced56d-0acd-46b4-9bf4-823b97554164", 441 | "name" : "role list", 442 | "protocol" : "saml", 443 | "protocolMapper" : "saml-role-list-mapper", 444 | "consentRequired" : false, 445 | "config" : { 446 | "single" : "false", 447 | "attribute.nameformat" : "Basic", 448 | "attribute.name" : "Role" 449 | } 450 | }, { 451 | "id" : "c1086b87-89d5-475b-b741-3829352b3a13", 452 | "name" : "username", 453 | "protocol" : "openid-connect", 454 | "protocolMapper" : "oidc-usermodel-property-mapper", 455 | "consentRequired" : true, 456 | "consentText" : "${username}", 457 | "config" : { 458 | "user.attribute" : "username", 459 | "id.token.claim" : "true", 460 | "access.token.claim" : "true", 461 | "claim.name" : "preferred_username", 462 | "Claim JSON Type" : "String" 463 | } 464 | }, { 465 | "id" : "26dfee5d-1814-4d6e-99a6-0f17b5a30042", 466 | "name" : "family name", 467 | "protocol" : "openid-connect", 468 | "protocolMapper" : "oidc-usermodel-property-mapper", 469 | "consentRequired" : true, 470 | "consentText" : "${familyName}", 471 | "config" : { 472 | "user.attribute" : "lastName", 473 | "id.token.claim" : "true", 474 | "access.token.claim" : "true", 475 | "claim.name" : "family_name", 476 | "Claim JSON Type" : "String" 477 | } 478 | }, { 479 | "id" : "f7f1ec2e-6142-4d1d-aa74-17d3f4e52a1c", 480 | "name" : "given name", 481 | "protocol" : "openid-connect", 482 | "protocolMapper" : "oidc-usermodel-property-mapper", 483 | "consentRequired" : true, 484 | "consentText" : "${givenName}", 485 | "config" : { 486 | "user.attribute" : "firstName", 487 | "id.token.claim" : "true", 488 | "access.token.claim" : "true", 489 | "claim.name" : "given_name", 490 | "Claim JSON Type" : "String" 491 | } 492 | }, { 493 | "id" : "57aec78e-3a0a-419d-a0c6-7ea7fca2a3e7", 494 | "name" : "full name", 495 | "protocol" : "openid-connect", 496 | "protocolMapper" : "oidc-full-name-mapper", 497 | "consentRequired" : true, 498 | "consentText" : "${fullName}", 499 | "config" : { 500 | "id.token.claim" : "true", 501 | "access.token.claim" : "true" 502 | } 503 | } ] 504 | }, { 505 | "id" : "34b0d631-a8fc-4902-9540-3d5e05163c51", 506 | "clientId" : "broker", 507 | "name" : "${client_broker}", 508 | "surrogateAuthRequired" : false, 509 | "enabled" : true, 510 | "secret" : "389731ae-8408-4589-b72e-c193032c159e", 511 | "redirectUris" : [ ], 512 | "webOrigins" : [ ], 513 | "notBefore" : 0, 514 | "bearerOnly" : false, 515 | "consentRequired" : false, 516 | "serviceAccountsEnabled" : false, 517 | "directGrantsOnly" : false, 518 | "publicClient" : false, 519 | "frontchannelLogout" : false, 520 | "attributes" : { }, 521 | "fullScopeAllowed" : false, 522 | "nodeReRegistrationTimeout" : 0, 523 | "protocolMappers" : [ { 524 | "id" : "a289c01e-9765-4ed9-a84f-a9adfb2a27f7", 525 | "name" : "username", 526 | "protocol" : "openid-connect", 527 | "protocolMapper" : "oidc-usermodel-property-mapper", 528 | "consentRequired" : true, 529 | "consentText" : "${username}", 530 | "config" : { 531 | "user.attribute" : "username", 532 | "id.token.claim" : "true", 533 | "access.token.claim" : "true", 534 | "claim.name" : "preferred_username", 535 | "Claim JSON Type" : "String" 536 | } 537 | }, { 538 | "id" : "4aadf15f-90d8-4f24-94ce-441a0aa818e3", 539 | "name" : "family name", 540 | "protocol" : "openid-connect", 541 | "protocolMapper" : "oidc-usermodel-property-mapper", 542 | "consentRequired" : true, 543 | "consentText" : "${familyName}", 544 | "config" : { 545 | "user.attribute" : "lastName", 546 | "id.token.claim" : "true", 547 | "access.token.claim" : "true", 548 | "claim.name" : "family_name", 549 | "Claim JSON Type" : "String" 550 | } 551 | }, { 552 | "id" : "82add1bf-e9fc-4fb8-8427-39d8a54f18d3", 553 | "name" : "role list", 554 | "protocol" : "saml", 555 | "protocolMapper" : "saml-role-list-mapper", 556 | "consentRequired" : false, 557 | "config" : { 558 | "single" : "false", 559 | "attribute.nameformat" : "Basic", 560 | "attribute.name" : "Role" 561 | } 562 | }, { 563 | "id" : "ab209f2a-cb68-45a4-af61-e9f1311b210c", 564 | "name" : "given name", 565 | "protocol" : "openid-connect", 566 | "protocolMapper" : "oidc-usermodel-property-mapper", 567 | "consentRequired" : true, 568 | "consentText" : "${givenName}", 569 | "config" : { 570 | "user.attribute" : "firstName", 571 | "id.token.claim" : "true", 572 | "access.token.claim" : "true", 573 | "claim.name" : "given_name", 574 | "Claim JSON Type" : "String" 575 | } 576 | }, { 577 | "id" : "7c16abee-1771-4ab2-9634-8108dd0152f4", 578 | "name" : "email", 579 | "protocol" : "openid-connect", 580 | "protocolMapper" : "oidc-usermodel-property-mapper", 581 | "consentRequired" : true, 582 | "consentText" : "${email}", 583 | "config" : { 584 | "user.attribute" : "email", 585 | "id.token.claim" : "true", 586 | "access.token.claim" : "true", 587 | "claim.name" : "email", 588 | "Claim JSON Type" : "String" 589 | } 590 | }, { 591 | "id" : "018f99ac-816b-4ab7-82c4-a95ccefb2141", 592 | "name" : "full name", 593 | "protocol" : "openid-connect", 594 | "protocolMapper" : "oidc-full-name-mapper", 595 | "consentRequired" : true, 596 | "consentText" : "${fullName}", 597 | "config" : { 598 | "id.token.claim" : "true", 599 | "access.token.claim" : "true" 600 | } 601 | } ] 602 | }, { 603 | "id" : "9b5b0a9e-0a6f-40ff-b0c6-c6f2a07b6744", 604 | "clientId" : "account", 605 | "name" : "${client_account}", 606 | "baseUrl" : "/auth/realms/booker/account", 607 | "surrogateAuthRequired" : false, 608 | "enabled" : true, 609 | "secret" : "da2cbaa5-de14-4a99-ad6f-60fe6d2e33d5", 610 | "defaultRoles" : [ "view-profile", "manage-account" ], 611 | "redirectUris" : [ "/auth/realms/booker/account/*" ], 612 | "webOrigins" : [ ], 613 | "notBefore" : 0, 614 | "bearerOnly" : false, 615 | "consentRequired" : false, 616 | "serviceAccountsEnabled" : false, 617 | "directGrantsOnly" : false, 618 | "publicClient" : false, 619 | "frontchannelLogout" : false, 620 | "attributes" : { }, 621 | "fullScopeAllowed" : false, 622 | "nodeReRegistrationTimeout" : 0, 623 | "protocolMappers" : [ { 624 | "id" : "731a4111-97ef-4f11-bf20-0b7abe060574", 625 | "name" : "family name", 626 | "protocol" : "openid-connect", 627 | "protocolMapper" : "oidc-usermodel-property-mapper", 628 | "consentRequired" : true, 629 | "consentText" : "${familyName}", 630 | "config" : { 631 | "user.attribute" : "lastName", 632 | "id.token.claim" : "true", 633 | "access.token.claim" : "true", 634 | "claim.name" : "family_name", 635 | "Claim JSON Type" : "String" 636 | } 637 | }, { 638 | "id" : "e084952e-a5a0-4b16-82d5-33fc7ed1c8f7", 639 | "name" : "email", 640 | "protocol" : "openid-connect", 641 | "protocolMapper" : "oidc-usermodel-property-mapper", 642 | "consentRequired" : true, 643 | "consentText" : "${email}", 644 | "config" : { 645 | "user.attribute" : "email", 646 | "id.token.claim" : "true", 647 | "access.token.claim" : "true", 648 | "claim.name" : "email", 649 | "Claim JSON Type" : "String" 650 | } 651 | }, { 652 | "id" : "00bec6d3-ae4e-46c5-8061-dd280730d749", 653 | "name" : "username", 654 | "protocol" : "openid-connect", 655 | "protocolMapper" : "oidc-usermodel-property-mapper", 656 | "consentRequired" : true, 657 | "consentText" : "${username}", 658 | "config" : { 659 | "user.attribute" : "username", 660 | "id.token.claim" : "true", 661 | "access.token.claim" : "true", 662 | "claim.name" : "preferred_username", 663 | "Claim JSON Type" : "String" 664 | } 665 | }, { 666 | "id" : "109b7e90-b8e4-4b01-986f-5f1497d3ed3e", 667 | "name" : "given name", 668 | "protocol" : "openid-connect", 669 | "protocolMapper" : "oidc-usermodel-property-mapper", 670 | "consentRequired" : true, 671 | "consentText" : "${givenName}", 672 | "config" : { 673 | "user.attribute" : "firstName", 674 | "id.token.claim" : "true", 675 | "access.token.claim" : "true", 676 | "claim.name" : "given_name", 677 | "Claim JSON Type" : "String" 678 | } 679 | }, { 680 | "id" : "291ab97d-6597-4055-9f62-3fe18bc0cc4e", 681 | "name" : "role list", 682 | "protocol" : "saml", 683 | "protocolMapper" : "saml-role-list-mapper", 684 | "consentRequired" : false, 685 | "config" : { 686 | "single" : "false", 687 | "attribute.nameformat" : "Basic", 688 | "attribute.name" : "Role" 689 | } 690 | }, { 691 | "id" : "1cd355db-2153-4458-8e28-049ed4563fd6", 692 | "name" : "full name", 693 | "protocol" : "openid-connect", 694 | "protocolMapper" : "oidc-full-name-mapper", 695 | "consentRequired" : true, 696 | "consentText" : "${fullName}", 697 | "config" : { 698 | "id.token.claim" : "true", 699 | "access.token.claim" : "true" 700 | } 701 | } ] 702 | } ], 703 | "browserSecurityHeaders" : { 704 | "contentSecurityPolicy" : "frame-src 'self'", 705 | "xFrameOptions" : "SAMEORIGIN" 706 | }, 707 | "smtpServer" : { }, 708 | "eventsEnabled" : false, 709 | "eventsListeners" : [ "jboss-logging" ], 710 | "enabledEventTypes" : [ ], 711 | "adminEventsEnabled" : false, 712 | "adminEventsDetailsEnabled" : false, 713 | "identityFederationEnabled" : false, 714 | "internationalizationEnabled" : false, 715 | "supportedLocales" : [ ], 716 | "authenticationFlows" : [ { 717 | "alias" : "direct grant", 718 | "description" : "OpenID Connect Resource Owner Grant", 719 | "providerId" : "basic-flow", 720 | "topLevel" : true, 721 | "builtIn" : true, 722 | "authenticationExecutions" : [ { 723 | "authenticator" : "direct-grant-validate-username", 724 | "autheticatorFlow" : false, 725 | "requirement" : "REQUIRED", 726 | "userSetupAllowed" : false, 727 | "priority" : 10 728 | }, { 729 | "authenticator" : "direct-grant-validate-password", 730 | "autheticatorFlow" : false, 731 | "requirement" : "REQUIRED", 732 | "userSetupAllowed" : false, 733 | "priority" : 20 734 | }, { 735 | "authenticator" : "direct-grant-validate-otp", 736 | "autheticatorFlow" : false, 737 | "requirement" : "OPTIONAL", 738 | "userSetupAllowed" : false, 739 | "priority" : 30 740 | } ] 741 | }, { 742 | "alias" : "registration", 743 | "description" : "registration flow", 744 | "providerId" : "basic-flow", 745 | "topLevel" : true, 746 | "builtIn" : true, 747 | "authenticationExecutions" : [ { 748 | "authenticator" : "registration-page-form", 749 | "flowAlias" : "registration form", 750 | "autheticatorFlow" : true, 751 | "requirement" : "REQUIRED", 752 | "userSetupAllowed" : false, 753 | "priority" : 10 754 | } ] 755 | }, { 756 | "alias" : "forms", 757 | "description" : "Username, password, otp and other auth forms.", 758 | "providerId" : "basic-flow", 759 | "topLevel" : false, 760 | "builtIn" : true, 761 | "authenticationExecutions" : [ { 762 | "authenticator" : "auth-username-password-form", 763 | "autheticatorFlow" : false, 764 | "requirement" : "REQUIRED", 765 | "userSetupAllowed" : false, 766 | "priority" : 10 767 | }, { 768 | "authenticator" : "auth-otp-form", 769 | "autheticatorFlow" : false, 770 | "requirement" : "OPTIONAL", 771 | "userSetupAllowed" : true, 772 | "priority" : 20 773 | } ] 774 | }, { 775 | "alias" : "registration form", 776 | "description" : "registration form", 777 | "providerId" : "form-flow", 778 | "topLevel" : false, 779 | "builtIn" : true, 780 | "authenticationExecutions" : [ { 781 | "authenticator" : "registration-user-creation", 782 | "autheticatorFlow" : false, 783 | "requirement" : "REQUIRED", 784 | "userSetupAllowed" : false, 785 | "priority" : 20 786 | }, { 787 | "authenticator" : "registration-profile-action", 788 | "autheticatorFlow" : false, 789 | "requirement" : "REQUIRED", 790 | "userSetupAllowed" : false, 791 | "priority" : 40 792 | }, { 793 | "authenticator" : "registration-password-action", 794 | "autheticatorFlow" : false, 795 | "requirement" : "REQUIRED", 796 | "userSetupAllowed" : false, 797 | "priority" : 50 798 | }, { 799 | "authenticator" : "registration-recaptcha-action", 800 | "autheticatorFlow" : false, 801 | "requirement" : "DISABLED", 802 | "userSetupAllowed" : false, 803 | "priority" : 60 804 | } ] 805 | }, { 806 | "alias" : "browser", 807 | "description" : "browser based authentication", 808 | "providerId" : "basic-flow", 809 | "topLevel" : true, 810 | "builtIn" : true, 811 | "authenticationExecutions" : [ { 812 | "authenticator" : "auth-cookie", 813 | "autheticatorFlow" : false, 814 | "requirement" : "ALTERNATIVE", 815 | "userSetupAllowed" : false, 816 | "priority" : 10 817 | }, { 818 | "authenticator" : "auth-spnego", 819 | "autheticatorFlow" : false, 820 | "requirement" : "DISABLED", 821 | "userSetupAllowed" : false, 822 | "priority" : 20 823 | }, { 824 | "flowAlias" : "forms", 825 | "autheticatorFlow" : true, 826 | "requirement" : "ALTERNATIVE", 827 | "userSetupAllowed" : false, 828 | "priority" : 30 829 | } ] 830 | } ], 831 | "authenticatorConfig" : [ ], 832 | "requiredActions" : [ { 833 | "alias" : "CONFIGURE_TOTP", 834 | "name" : "Configure Totp", 835 | "providerId" : "CONFIGURE_TOTP", 836 | "enabled" : true, 837 | "defaultAction" : false, 838 | "config" : { } 839 | }, { 840 | "alias" : "UPDATE_PROFILE", 841 | "name" : "Update Profile", 842 | "providerId" : "UPDATE_PROFILE", 843 | "enabled" : true, 844 | "defaultAction" : false, 845 | "config" : { } 846 | }, { 847 | "alias" : "UPDATE_PASSWORD", 848 | "name" : "Update Password", 849 | "providerId" : "UPDATE_PASSWORD", 850 | "enabled" : true, 851 | "defaultAction" : false, 852 | "config" : { } 853 | }, { 854 | "alias" : "terms_and_conditions", 855 | "name" : "Terms and Conditions", 856 | "providerId" : "terms_and_conditions", 857 | "enabled" : false, 858 | "defaultAction" : false, 859 | "config" : { } 860 | }, { 861 | "alias" : "VERIFY_EMAIL", 862 | "name" : "Verify Email", 863 | "providerId" : "VERIFY_EMAIL", 864 | "enabled" : true, 865 | "defaultAction" : false, 866 | "config" : { } 867 | } ] 868 | } 869 | -------------------------------------------------------------------------------- /extra/logstash/README.md: -------------------------------------------------------------------------------- 1 | # Download Logstash 2 | 3 | https://download.elastic.co/logstash/logstash/logstash-1.5.3.zip 4 | 5 | # Download Kibana 6 | 7 | Select Kibana for the appropriate platform: 8 | 9 | https://www.elastic.co/downloads/kibana 10 | 11 | # Launch Logstash 12 | 13 | Using the included `logstash-wildfly.conf` 14 | 15 | ./bin/logstash agent -f logstash-wildfly.conf 16 | 17 | # Launch Kibana 18 | 19 | ./bin/kibana 20 | 21 | # Access Kibana 22 | 23 | http://localhost:5601 24 | -------------------------------------------------------------------------------- /extra/logstash/logstash-wildfly.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | port => 8000 4 | } 5 | } 6 | 7 | filter { 8 | json { 9 | source => "message" 10 | } 11 | } 12 | 13 | output { 14 | 15 | elasticsearch { 16 | # Use the embedded elsasticsearch for convienence 17 | embedded => true 18 | protocol => "http" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | -------------------------------------------------------------------------------- /library/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/base-jdk:8 2 | 3 | USER root 4 | RUN yum install -y iproute 5 | ADD launch.sh /usr/bin/launch.sh 6 | RUN chmod +x /usr/bin/launch.sh 7 | RUN mkdir -p /opt/booker 8 | RUN chown jboss:jboss /opt/booker 9 | 10 | USER jboss 11 | ADD booker-library-swarm.jar /opt/booker/booker-library.jar 12 | 13 | EXPOSE 8080 14 | 15 | ENTRYPOINT /usr/bin/launch.sh 16 | -------------------------------------------------------------------------------- /library/docker/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IPADDR=$(ip a s | sed -ne '/127.0.0.1/!{s/^[ \t]*inet[ \t]*\([0-9.]\+\)\/.*$/\1/p}') 4 | 5 | /usr/bin/java -Dswarm.http.port=8080 -Dswarm.bind.address=$IPADDR $SWARM_JVM_ARGS -jar /opt/booker/booker-library.jar 6 | -------------------------------------------------------------------------------- /library/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 4.0.0 10 | 11 | 12 | org.wildfly.swarm.booker 13 | booker-parent 14 | 1.0.0.Alpha01-SNAPSHOT 15 | ../ 16 | 17 | 18 | booker-library 19 | 20 | Booker: Library 21 | Booker: Library 22 | 23 | jar 24 | 25 | 26 | 27 | 28 | org.wildfly.swarm 29 | wildfly-swarm-plugin 30 | 31 | org.wildfly.swarm.booker.library.Main 32 | 33 | ${library.port.offset} 34 | 35 | 36 | 37 | 38 | 39 | package 40 | 41 | 42 | 43 | 44 | 45 | com.spotify 46 | docker-maven-plugin 47 | 48 | false 49 | booker/${project.artifactId} 50 | 51 | ${project.version} 52 | 53 | ${project.basedir}/docker 54 | 55 | 56 | / 57 | ${project.build.directory} 58 | ${project.build.finalName}-swarm.jar 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.wildfly.swarm 69 | jaxrs 70 | 71 | 72 | org.wildfly.swarm 73 | cdi 74 | 75 | 76 | org.wildfly.swarm 77 | jpa 78 | 79 | 80 | org.wildfly.swarm 81 | ejb 82 | 83 | 84 | org.wildfly.swarm.booker 85 | booker-common 86 | 87 | 88 | com.fasterxml.jackson.core 89 | jackson-databind 90 | 2.6.0-rc4 91 | 92 | 93 | com.h2database 94 | h2 95 | 1.4.192 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /library/src/main/java/org/wildfly/swarm/booker/library/LibraryItem.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.library; 2 | 3 | import javax.persistence.*; 4 | 5 | /** 6 | * @author Bob McWhirter 7 | */ 8 | @Entity 9 | @Table(name="LibraryItem") 10 | public class LibraryItem { 11 | 12 | @Id 13 | @GeneratedValue(strategy = GenerationType.AUTO) 14 | private int id; 15 | 16 | @Column 17 | private String userId; 18 | 19 | @Column 20 | private String bookId; 21 | 22 | @Transient 23 | private String title; 24 | 25 | @Transient 26 | private String author; 27 | 28 | public LibraryItem() { 29 | 30 | } 31 | 32 | public LibraryItem(String userId, String bookId) { 33 | this.userId = userId; 34 | this.bookId = bookId; 35 | } 36 | 37 | public String getUserId() { 38 | return this.userId; 39 | } 40 | 41 | public void setUserId(String userId) { 42 | this.userId = userId; 43 | } 44 | 45 | public String getBookId() { 46 | return this.bookId; 47 | } 48 | 49 | public void setBookId(String bookId) { 50 | this.bookId = bookId; 51 | } 52 | 53 | public String getTitle() { 54 | return this.title; 55 | } 56 | 57 | public void setTitle(String title) { 58 | this.title = title; 59 | } 60 | 61 | public String getAuthor() { 62 | return this.author; 63 | } 64 | 65 | public void setAuthor(String author) { 66 | this.author = author; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /library/src/main/java/org/wildfly/swarm/booker/library/LibraryResource.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.library; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import javax.ejb.Stateless; 10 | import javax.inject.Inject; 11 | import javax.persistence.EntityManager; 12 | import javax.persistence.TypedQuery; 13 | import javax.ws.rs.*; 14 | import javax.ws.rs.container.AsyncResponse; 15 | import javax.ws.rs.container.Suspended; 16 | import javax.ws.rs.core.Context; 17 | import javax.ws.rs.core.MediaType; 18 | import javax.ws.rs.core.SecurityContext; 19 | 20 | import com.fasterxml.jackson.core.JsonFactory; 21 | import com.fasterxml.jackson.core.JsonParser; 22 | import com.fasterxml.jackson.databind.ObjectMapper; 23 | import com.fasterxml.jackson.databind.ObjectReader; 24 | import io.netty.buffer.ByteBuf; 25 | import io.netty.buffer.ByteBufInputStream; 26 | import org.keycloak.KeycloakPrincipal; 27 | import rx.Observable; 28 | 29 | /** 30 | * @author Bob McWhirter 31 | */ 32 | @Path("/") 33 | @Stateless 34 | public class LibraryResource { 35 | 36 | @Inject 37 | StoreService store; 38 | 39 | @Inject 40 | EntityManager em; 41 | 42 | @GET 43 | @Produces(MediaType.APPLICATION_JSON) 44 | @Path("/items") 45 | public void get(@Suspended final AsyncResponse asyncResponse, @Context SecurityContext context) { 46 | KeycloakPrincipal principal = (KeycloakPrincipal) context.getUserPrincipal(); 47 | String userId = principal.getName(); 48 | TypedQuery q = this.em.createQuery("SELECT li FROM LibraryItem li WHERE li.userId = :userId", LibraryItem.class); 49 | List items = q.setParameter("userId", userId).getResultList(); 50 | 51 | Observable> root = Observable.just(new ArrayList<>()); 52 | for (LibraryItem each : items) { 53 | Observable obs = store.get(each.getBookId()).observe(); 54 | root = root.zipWith(obs, ((libraryItems, byteBuf) -> { 55 | ObjectMapper mapper = new ObjectMapper(); 56 | ObjectReader reader = mapper.reader(); 57 | JsonFactory factory = new JsonFactory(); 58 | try { 59 | JsonParser parser = factory.createParser(new ByteBufInputStream(byteBuf)); 60 | Map map = reader.readValue(parser, Map.class); 61 | each.setTitle((String) map.get("title")); 62 | each.setAuthor((String) map.get("author")); 63 | } catch (IOException e) { 64 | System.err.println("Error: " + e.getLocalizedMessage()); 65 | } 66 | libraryItems.add(each); 67 | return libraryItems; 68 | })); 69 | } 70 | 71 | root.subscribe( 72 | asyncResponse::resume, 73 | asyncResponse::resume 74 | ); 75 | } 76 | 77 | @POST 78 | @Consumes(MediaType.APPLICATION_JSON) 79 | @Produces(MediaType.APPLICATION_JSON) 80 | @Path("/items") 81 | public LibraryItem addItem(@Context SecurityContext context, LibraryItem item) throws URISyntaxException { 82 | KeycloakPrincipal principal = (KeycloakPrincipal) context.getUserPrincipal(); 83 | item.setUserId(principal.getName()); 84 | em.persist(item); 85 | return item; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /library/src/main/java/org/wildfly/swarm/booker/library/Main.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.library; 2 | 3 | import org.jboss.shrinkwrap.api.ShrinkWrap; 4 | import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset; 5 | import org.wildfly.swarm.Swarm; 6 | import org.wildfly.swarm.booker.common.ContainerUtils; 7 | import org.wildfly.swarm.jaxrs.JAXRSArchive; 8 | import org.wildfly.swarm.jpa.JPAFraction; 9 | import org.wildfly.swarm.keycloak.Secured; 10 | import org.wildfly.swarm.netflix.ribbon.RibbonArchive; 11 | 12 | /** 13 | * @author Bob McWhirter 14 | */ 15 | public class Main { 16 | 17 | public static void main(String... args) throws Exception { 18 | 19 | Swarm container = new Swarm(); 20 | container.fraction(ContainerUtils.loggingFraction()); 21 | container.fraction(new JPAFraction() 22 | .defaultDatasource("LibraryDS")); 23 | 24 | JAXRSArchive deployment = ShrinkWrap.create(JAXRSArchive.class); 25 | deployment.addPackage(Main.class.getPackage()); 26 | deployment.as(RibbonArchive.class).advertise("library"); 27 | deployment.as(Secured.class) 28 | .protect("/items") 29 | .withMethod("GET") 30 | .withRole("*"); 31 | ContainerUtils.addExternalKeycloakJson(deployment); 32 | 33 | 34 | deployment.add(new ClassLoaderAsset("META-INF/persistence.xml", Main.class.getClassLoader()), "WEB-INF/classes/META-INF/persistence.xml"); 35 | deployment.add(new ClassLoaderAsset("project-stages.yml", Main.class.getClassLoader()), "WEB-INF/classes/project-stages.yml"); 36 | deployment.addAllDependencies(); 37 | container.start(); 38 | container.deploy(deployment); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /library/src/main/java/org/wildfly/swarm/booker/library/PersistenceHelper.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.library; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | import javax.enterprise.inject.Produces; 5 | import javax.persistence.EntityManager; 6 | import javax.persistence.PersistenceContext; 7 | 8 | /** 9 | * @author Bob McWhirter 10 | */ 11 | @ApplicationScoped 12 | public class PersistenceHelper { 13 | 14 | @PersistenceContext 15 | private EntityManager em; 16 | 17 | @Produces 18 | public EntityManager getEntityManager() { 19 | return em; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /library/src/main/java/org/wildfly/swarm/booker/library/ServicesFactory.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.library; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | import javax.enterprise.inject.Produces; 5 | 6 | import org.wildfly.swarm.netflix.ribbon.secured.client.SecuredRibbon; 7 | 8 | /** 9 | * @author Bob McWhirter 10 | */ 11 | @ApplicationScoped 12 | public class ServicesFactory { 13 | 14 | @Produces 15 | @ApplicationScoped 16 | public static StoreService getInstance() { 17 | return SecuredRibbon.from(StoreService.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /library/src/main/java/org/wildfly/swarm/booker/library/StoreService.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.library; 2 | 3 | import com.netflix.ribbon.RibbonRequest; 4 | import com.netflix.ribbon.proxy.annotation.Http; 5 | import com.netflix.ribbon.proxy.annotation.ResourceGroup; 6 | import com.netflix.ribbon.proxy.annotation.TemplateName; 7 | import com.netflix.ribbon.proxy.annotation.Var; 8 | import io.netty.buffer.ByteBuf; 9 | 10 | /** 11 | * @author Bob McWhirter 12 | */ 13 | @ResourceGroup(name = "store") 14 | public interface StoreService { 15 | 16 | @TemplateName("get") 17 | @Http( 18 | method = Http.HttpMethod.GET, 19 | uri = "/book?id={bookId}" 20 | ) 21 | RibbonRequest get(@Var("bookId")String bookId); 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /library/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /library/src/main/resources/keycloak.json: -------------------------------------------------------------------------------- 1 | { 2 | "realm": "booker", 3 | "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm/yXPhm3xlNsYOLQGM4YxqjAe5mjBxKcxJCYyQGzz36DQFO59IclAxaFEsgN3OorVL2W9wLLGUveoV47s8rOdq4+obHiO2C/bgDSVcvg+X8RRAngZDR04iPPdD+cjMdxAYb/WsGjlOKju+U8Pk2o/TnRHNmZgbwE9JDnhGrmFycCgu7oQGk6KDVpsp3zVIsr2qrah0ujBwUbPti8NN4OZBupMzgR3oOjzJ9dhkh9qaQN6SErnRj3lENAh7rbizKEWnIxImgi6m6ogLNWMxNKJlrRbER2LCSegDXhhO3zuBhHb0xZ1P+dO+4LL478SCQStutrGSoO0Yc0GiEVFBIncQIDAQAB", 4 | "auth-server-url": "http://localhost:9090/auth", 5 | "ssl-required": "external", 6 | "resource": "web-client", 7 | "credentials": { 8 | "secret": "deec63e4-d242-4180-b402-80fba0a9187e" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /library/src/main/resources/project-stages.yml: -------------------------------------------------------------------------------- 1 | swarm: 2 | ds: 3 | name: 4 | LibraryDS 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.wildfly.swarm.booker 7 | booker-parent 8 | 1.0.0.Alpha01-SNAPSHOT 9 | 10 | Reader: Parent 11 | Reader: Parent 12 | 13 | pom 14 | 15 | 16 | 2016.12.0 17 | 5.1.0.Final 18 | 19 | 3.2.5 20 | 21 | 1.8 22 | 1.8 23 | 24 | UTF-8 25 | 26 | 1.1.10.Final 27 | 4.12 28 | 3.0.19.Final 29 | 30 | 0 31 | 0 32 | 0 33 | 34 | 35 | 36 | ${project.artifactId} 37 | 38 | 39 | 40 | 41 | org.wildfly.swarm 42 | wildfly-swarm-plugin 43 | ${version.wildfly-swarm} 44 | 45 | 46 | 127.0.0.1 47 | true 48 | ${project.artifactId} 49 | 53 | 54 | 55 | -Xmx128m 56 | 57 | true 58 | 59 | 60 | 61 | 62 | package 63 | 64 | 65 | 66 | 67 | 68 | com.spotify 69 | docker-maven-plugin 70 | 0.4.10 71 | 72 | true 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | consul 82 | 83 | 84 | swarm.consul.url 85 | 86 | 87 | 88 | 89 | org.wildfly.swarm 90 | topology-consul 91 | 92 | 93 | 94 | 95 | jgroups 96 | 97 | true 98 | 99 | 100 | 4 101 | 3 102 | 2 103 | 104 | 105 | 106 | org.wildfly.swarm 107 | topology-jgroups 108 | 109 | 110 | 111 | 112 | openshift 113 | 114 | 115 | org.wildfly.swarm 116 | topology-openshift 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | org.wildfly.swarm 126 | bom-all 127 | ${version.wildfly-swarm} 128 | pom 129 | import 130 | 131 | 132 | 133 | org.wildfly.swarm.servers 134 | keycloak 135 | ${version.wildfly-swarm} 136 | 137 | 138 | 139 | org.wildfly.swarm.booker 140 | booker-common 141 | ${project.version} 142 | 143 | 144 | 145 | com.openshift 146 | openshift-restclient-java 147 | ${version.openshift.client} 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | org.wildfly.swarm 157 | ribbon-secured 158 | 159 | 160 | org.wildfly.swarm 161 | logstash 162 | 163 | 164 | 165 | 166 | common 167 | 168 | store 169 | pricing 170 | library 171 | 172 | web-client 173 | 174 | 175 | 176 | 177 | bees-snapshot-repository-group 178 | CloudBees Snapshot Repository Group 179 | http://repository-projectodd.forge.cloudbees.com/snapshot/ 180 | 181 | false 182 | 183 | 184 | true 185 | 186 | 187 | 188 | 189 | 190 | 191 | bees-snapshot-repository-group 192 | CloudBees Snapshot Repository Group 193 | http://repository-projectodd.forge.cloudbees.com/snapshot/ 194 | 195 | false 196 | 197 | 198 | true 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /pricing/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/base-jdk:8 2 | 3 | USER root 4 | RUN yum install -y iproute 5 | ADD launch.sh /usr/bin/launch.sh 6 | RUN chmod +x /usr/bin/launch.sh 7 | RUN mkdir -p /opt/booker 8 | RUN chown jboss:jboss /opt/booker 9 | 10 | USER jboss 11 | ADD booker-pricing-swarm.jar /opt/booker/booker-pricing.jar 12 | 13 | EXPOSE 8080 14 | 15 | ENTRYPOINT /usr/bin/launch.sh 16 | -------------------------------------------------------------------------------- /pricing/docker/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IPADDR=$(ip a s | sed -ne '/127.0.0.1/!{s/^[ \t]*inet[ \t]*\([0-9.]\+\)\/.*$/\1/p}') 4 | 5 | /usr/bin/java -Dswarm.http.port=8080 -Dswarm.bind.address=$IPADDR $SWARM_JVM_ARGS -jar /opt/booker/booker-pricing.jar 6 | -------------------------------------------------------------------------------- /pricing/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 4.0.0 10 | 11 | 12 | org.wildfly.swarm.booker 13 | booker-parent 14 | 1.0.0.Alpha01-SNAPSHOT 15 | ../ 16 | 17 | 18 | booker-pricing 19 | 20 | Booker: Pricing 21 | Booker: Pricing 22 | 23 | jar 24 | 25 | 26 | 27 | 28 | org.wildfly.swarm 29 | wildfly-swarm-plugin 30 | 31 | org.wildfly.swarm.booker.pricing.Main 32 | 33 | ${pricing.port.offset} 34 | 35 | 36 | 37 | 38 | 39 | package 40 | 41 | 42 | 43 | 44 | 45 | com.spotify 46 | docker-maven-plugin 47 | 48 | false 49 | booker/${project.artifactId} 50 | 51 | ${project.version} 52 | 53 | ${project.basedir}/docker 54 | 55 | 56 | / 57 | ${project.build.directory} 58 | ${project.build.finalName}-swarm.jar 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.wildfly.swarm 69 | jaxrs 70 | 71 | 72 | org.wildfly.swarm.booker 73 | booker-common 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /pricing/src/main/java/org/wildfly/swarm/booker/pricing/Main.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.pricing; 2 | 3 | import org.jboss.shrinkwrap.api.ShrinkWrap; 4 | import org.wildfly.swarm.Swarm; 5 | import org.wildfly.swarm.container.Container; 6 | import org.wildfly.swarm.jaxrs.JAXRSArchive; 7 | import org.wildfly.swarm.keycloak.Secured; 8 | import org.wildfly.swarm.netflix.ribbon.RibbonArchive; 9 | import org.wildfly.swarm.booker.common.ContainerUtils; 10 | 11 | /** 12 | * @author Bob McWhirter 13 | */ 14 | public class Main { 15 | 16 | public static void main(String... args) throws Exception { 17 | 18 | Swarm container = new Swarm(); 19 | container.fraction(ContainerUtils.loggingFraction()); 20 | 21 | JAXRSArchive deployment = ShrinkWrap.create(JAXRSArchive.class); 22 | deployment.addPackage(Main.class.getPackage()); 23 | deployment.as(RibbonArchive.class).advertise("pricing"); 24 | deployment.as(Secured.class); 25 | ContainerUtils.addExternalKeycloakJson(deployment); 26 | 27 | container.start(); 28 | container.deploy(deployment); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pricing/src/main/java/org/wildfly/swarm/booker/pricing/PricingResource.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.pricing; 2 | 3 | import org.keycloak.KeycloakPrincipal; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | import javax.ws.rs.PathParam; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.Context; 10 | import javax.ws.rs.core.MediaType; 11 | import javax.ws.rs.core.SecurityContext; 12 | 13 | /** 14 | * @author Bob McWhirter 15 | */ 16 | @Path("/") 17 | public class PricingResource { 18 | 19 | @GET 20 | @Path("/book/{id}") 21 | @Produces(MediaType.APPLICATION_JSON) 22 | public Integer search(@PathParam("id") String id, @Context SecurityContext context) { 23 | KeycloakPrincipal principal = (KeycloakPrincipal) context.getUserPrincipal(); 24 | if ( principal != null && principal.getKeycloakSecurityContext() != null ) { 25 | return 9; 26 | } 27 | return 10; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pricing/src/main/resources/keycloak.json: -------------------------------------------------------------------------------- 1 | { 2 | "realm": "booker", 3 | "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm/yXPhm3xlNsYOLQGM4YxqjAe5mjBxKcxJCYyQGzz36DQFO59IclAxaFEsgN3OorVL2W9wLLGUveoV47s8rOdq4+obHiO2C/bgDSVcvg+X8RRAngZDR04iPPdD+cjMdxAYb/WsGjlOKju+U8Pk2o/TnRHNmZgbwE9JDnhGrmFycCgu7oQGk6KDVpsp3zVIsr2qrah0ujBwUbPti8NN4OZBupMzgR3oOjzJ9dhkh9qaQN6SErnRj3lENAh7rbizKEWnIxImgi6m6ogLNWMxNKJlrRbER2LCSegDXhhO3zuBhHb0xZ1P+dO+4LL478SCQStutrGSoO0Yc0GiEVFBIncQIDAQAB", 4 | "auth-server-url": "http://localhost:9090/auth", 5 | "ssl-required": "external", 6 | "resource": "web-client", 7 | "credentials": { 8 | "secret": "deec63e4-d242-4180-b402-80fba0a9187e" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /store/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/base-jdk:8 2 | 3 | USER root 4 | RUN yum install -y iproute 5 | ADD launch.sh /usr/bin/launch.sh 6 | RUN chmod +x /usr/bin/launch.sh 7 | RUN mkdir -p /opt/booker 8 | RUN chown jboss:jboss /opt/booker 9 | 10 | USER jboss 11 | ADD booker-store-swarm.jar /opt/booker/booker-store.jar 12 | 13 | EXPOSE 8080 14 | 15 | ENTRYPOINT /usr/bin/launch.sh 16 | -------------------------------------------------------------------------------- /store/docker/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IPADDR=$(ip a s | sed -ne '/127.0.0.1/!{s/^[ \t]*inet[ \t]*\([0-9.]\+\)\/.*$/\1/p}') 4 | 5 | /usr/bin/java -Dswarm.http.port=8080 -Dswarm.bind.address=$IPADDR $SWARM_JVM_ARGS -jar /opt/booker/booker-store.jar 6 | -------------------------------------------------------------------------------- /store/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 4.0.0 10 | 11 | 12 | org.wildfly.swarm.booker 13 | booker-parent 14 | 1.0.0.Alpha01-SNAPSHOT 15 | ../ 16 | 17 | 18 | booker-store 19 | 20 | Booker: Store 21 | Booker: Store 22 | 23 | jar 24 | 25 | 26 | 27 | 28 | org.wildfly.swarm 29 | wildfly-swarm-plugin 30 | 31 | org.wildfly.swarm.booker.store.Main 32 | 33 | ${store.port.offset} 34 | 35 | 36 | 37 | 38 | 39 | package 40 | 41 | 42 | 43 | 44 | 45 | com.spotify 46 | docker-maven-plugin 47 | 48 | false 49 | booker/${project.artifactId} 50 | 51 | ${project.version} 52 | 53 | ${project.basedir}/docker 54 | 55 | 56 | / 57 | ${project.build.directory} 58 | ${project.build.finalName}-swarm.jar 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.wildfly.swarm 69 | jaxrs 70 | 71 | 72 | org.wildfly.swarm 73 | cdi 74 | 75 | 76 | org.wildfly.swarm 77 | zipkin-jaxrs 78 | 79 | 80 | org.wildfly.swarm.booker 81 | booker-common 82 | 83 | 84 | 85 | org.wildfly.swarm 86 | arquillian 87 | ${version.wildfly-swarm} 88 | test 89 | 90 | 91 | org.jboss.arquillian.junit 92 | arquillian-junit-container 93 | ${version.org.arquillian} 94 | test 95 | 96 | 97 | org.jboss.resteasy 98 | resteasy-client 99 | ${version.resteasy} 100 | test 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/Book.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | /** 4 | * @author Bob McWhirter 5 | */ 6 | public class Book { 7 | 8 | private final String id; 9 | 10 | private final String title; 11 | 12 | private final String author; 13 | 14 | private final String url; 15 | 16 | private int price; 17 | 18 | public Book(String id, String title, String author, String url) { 19 | this.id = id; 20 | this.title = title; 21 | this.author = author; 22 | this.url = url; 23 | } 24 | 25 | public String getId() { 26 | return this.id; 27 | } 28 | 29 | public String getTitle() { 30 | return this.title; 31 | } 32 | 33 | public String getAuthor() { 34 | return this.author; 35 | } 36 | 37 | public void setPrice(int price) { 38 | this.price = price; 39 | } 40 | 41 | public int getPrice() { 42 | return this.price; 43 | } 44 | 45 | public String toString() { 46 | return "[" + this.id + ": " + this.title + "]"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/Main.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileVisitResult; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.nio.file.SimpleFileVisitor; 9 | import java.nio.file.attribute.BasicFileAttributes; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | import javax.xml.stream.XMLStreamException; 13 | 14 | import org.jboss.shrinkwrap.api.ShrinkWrap; 15 | import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset; 16 | import org.wildfly.swarm.Swarm; 17 | import org.wildfly.swarm.booker.common.ContainerUtils; 18 | import org.wildfly.swarm.jaxrs.JAXRSArchive; 19 | import org.wildfly.swarm.jaxrs.btm.ZipkinFraction; 20 | import org.wildfly.swarm.keycloak.Secured; 21 | import org.wildfly.swarm.netflix.ribbon.RibbonArchive; 22 | 23 | /** 24 | * @author Bob McWhirter 25 | */ 26 | public class Main { 27 | 28 | public static void main(String... args) throws Exception { 29 | 30 | if (System.getProperty("swarm.gutenberg.data") != null) { 31 | //int limit = Integer.MAX_VALUE; 32 | int limit = 5000; 33 | AtomicInteger counter = new AtomicInteger(0); 34 | RDFProcessor processor = new RDFProcessor(Paths.get("src", "main", "resources", "META-INF", "store.xml")); 35 | Files.walkFileTree(Paths.get(System.getProperty("swarm.gutenberg.data")), new SimpleFileVisitor() { 36 | @Override 37 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 38 | try { 39 | processor.process(file); 40 | } catch (XMLStreamException e) { 41 | e.printStackTrace(); 42 | } 43 | if (counter.incrementAndGet() > limit) { 44 | return FileVisitResult.TERMINATE; 45 | } 46 | if (counter.get() % 1000 == 0) { 47 | System.err.print('.'); 48 | } 49 | return super.visitFile(file, attrs); 50 | } 51 | }); 52 | 53 | processor.close(); 54 | System.exit(0); 55 | } 56 | 57 | 58 | Swarm container = new Swarm(); 59 | container.fraction(ContainerUtils.loggingFraction()); 60 | /* 61 | enable this for remote zipkin reporting 62 | 63 | container.fraction( 64 | new ZipkinFraction("booker-store") 65 | .reportAsync("http://localhost:9411/api/v1/spans") 66 | .sampleRate(0.1f) // keep 10% 67 | );*/ 68 | 69 | container.fraction(new ZipkinFraction("booker-store")); 70 | container.start(); 71 | 72 | JAXRSArchive deployment = ShrinkWrap.create(JAXRSArchive.class); 73 | deployment.addPackage(Main.class.getPackage()); 74 | deployment.addAsWebInfResource(new ClassLoaderAsset("WEB-INF/web.xml", Main.class.getClassLoader()), "web.xml"); 75 | deployment.add(new ClassLoaderAsset("META-INF/store.xml", Main.class.getClassLoader()), "WEB-INF/classes/store.xml"); 76 | deployment.as(RibbonArchive.class).advertise("store"); 77 | deployment.as(Secured.class); 78 | ContainerUtils.addExternalKeycloakJson(deployment); 79 | deployment.addAllDependencies(); 80 | 81 | 82 | container.deploy(deployment); 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/PricingService.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | import com.netflix.ribbon.RibbonRequest; 4 | import com.netflix.ribbon.proxy.annotation.Http; 5 | import com.netflix.ribbon.proxy.annotation.ResourceGroup; 6 | import com.netflix.ribbon.proxy.annotation.TemplateName; 7 | import com.netflix.ribbon.proxy.annotation.Var; 8 | import io.netty.buffer.ByteBuf; 9 | 10 | /** 11 | * @author Bob McWhirter 12 | */ 13 | @ResourceGroup(name="pricing") 14 | public interface PricingService { 15 | 16 | @TemplateName("get") 17 | @Http( 18 | method = Http.HttpMethod.GET, 19 | uri = "/book/{id}" 20 | ) 21 | RibbonRequest get(@Var("id") String id); 22 | } 23 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/RDFProcessor.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileOutputStream; 6 | import java.nio.file.Path; 7 | 8 | import javax.xml.namespace.QName; 9 | import javax.xml.stream.XMLEventWriter; 10 | import javax.xml.stream.XMLInputFactory; 11 | import javax.xml.stream.XMLOutputFactory; 12 | import javax.xml.stream.XMLStreamException; 13 | import javax.xml.stream.XMLStreamReader; 14 | import javax.xml.stream.XMLStreamWriter; 15 | 16 | import static javax.xml.stream.XMLStreamReader.*; 17 | 18 | /** 19 | * @author Bob McWhirter 20 | */ 21 | public class RDFProcessor { 22 | 23 | public static final String DC_TERMS = "http://purl.org/dc/terms/"; 24 | 25 | public static final String PG_TERMS = "http://www.gutenberg.org/2009/pgterms/"; 26 | 27 | public static final String RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; 28 | 29 | public static final QName EBOOK = new QName(PG_TERMS, "ebook"); 30 | 31 | public static final QName TITLE = new QName(DC_TERMS, "title"); 32 | 33 | public static final QName HAS_FORMAT = new QName(DC_TERMS, "hasFormat"); 34 | 35 | public static final QName FILE = new QName(PG_TERMS, "file"); 36 | 37 | public static final QName FORMAT = new QName(DC_TERMS, "format"); 38 | 39 | public static final QName DESCRIPTION = new QName(RDF, "Description"); 40 | 41 | public static final QName VALUE = new QName(RDF, "value"); 42 | 43 | public static final QName CREATOR = new QName(DC_TERMS, "creator"); 44 | 45 | public static final QName AGENT = new QName(PG_TERMS, "agent"); 46 | 47 | public static final QName NAME = new QName(PG_TERMS, "name"); 48 | 49 | private final XMLStreamWriter out; 50 | 51 | private String title; 52 | 53 | private String url; 54 | 55 | private String creator; 56 | 57 | private String format; 58 | 59 | private String id; 60 | 61 | 62 | public RDFProcessor(Path outputPath) throws FileNotFoundException, XMLStreamException { 63 | XMLOutputFactory f = XMLOutputFactory.newFactory(); 64 | this.out = f.createXMLStreamWriter(new FileOutputStream(outputPath.toFile())); 65 | this.out.writeStartDocument(); 66 | this.out.writeCharacters("\n"); 67 | this.out.writeStartElement("library"); 68 | } 69 | 70 | public void close() throws XMLStreamException { 71 | this.out.writeEndElement(); 72 | this.out.writeEndDocument(); 73 | this.out.close(); 74 | } 75 | 76 | public void process(Path path) throws FileNotFoundException, XMLStreamException { 77 | this.title = null; 78 | this.url = null; 79 | this.creator = null; 80 | this.format = null; 81 | 82 | XMLInputFactory f = XMLInputFactory.newInstance(); 83 | XMLStreamReader r = f.createXMLStreamReader(new FileInputStream(path.toFile())); 84 | while (r.hasNext()) { 85 | r.next(); 86 | switch (r.getEventType()) { 87 | case START_DOCUMENT: 88 | break; 89 | case END_DOCUMENT: 90 | break; 91 | 92 | case START_ELEMENT: 93 | if (r.getName().equals(EBOOK)) { 94 | parseEbook(r); 95 | } 96 | break; 97 | case END_ELEMENT: 98 | break; 99 | 100 | } 101 | } 102 | 103 | r.close(); 104 | 105 | if (this.title != null) { 106 | this.out.writeStartElement("book"); 107 | this.out.writeAttribute( "id", this.id ); 108 | this.out.writeCharacters("\n"); 109 | 110 | this.out.writeCharacters(" "); 111 | this.out.writeStartElement("title"); 112 | this.out.writeCharacters(this.title); 113 | this.out.writeEndElement(); 114 | this.out.writeCharacters("\n"); 115 | 116 | this.out.writeCharacters(" "); 117 | this.out.writeStartElement("author"); 118 | this.out.writeCharacters(this.creator); 119 | this.out.writeEndElement(); 120 | this.out.writeCharacters("\n"); 121 | 122 | this.out.writeCharacters(" "); 123 | this.out.writeStartElement("url"); 124 | this.out.writeCharacters(this.url); 125 | this.out.writeEndElement(); 126 | this.out.writeCharacters("\n"); 127 | 128 | this.out.writeEndElement(); 129 | this.out.writeCharacters("\n"); 130 | } 131 | 132 | /* 133 | System.err.println( "---" ); 134 | System.err.println( this.title ); 135 | System.err.println( this.creator ); 136 | System.err.println( this.url ); 137 | System.err.println( this.format ); 138 | */ 139 | } 140 | 141 | protected void parseEbook(XMLStreamReader r) throws XMLStreamException { 142 | 143 | String about = r.getAttributeValue(RDF, "about"); 144 | String[] parts = about.split("/"); 145 | 146 | this.id = parts[1]; 147 | 148 | this.url = "http://www.gutenberg.org/cache/epub/" + this.id + "/pg" + this.id + ".html"; 149 | while (r.hasNext()) { 150 | r.next(); 151 | switch (r.getEventType()) { 152 | case START_ELEMENT: 153 | /* 154 | if (r.getName().equals(HAS_FORMAT)) { 155 | if ( this.format == null ) { 156 | parseHasFormat(r); 157 | } 158 | } 159 | */ 160 | if (r.getName().equals(TITLE)) { 161 | parseTitle(r); 162 | } 163 | if (r.getName().equals(CREATOR)) { 164 | parseCreator(r); 165 | } 166 | break; 167 | case END_ELEMENT: 168 | if (r.getName().equals(EBOOK)) { 169 | return; 170 | } 171 | break; 172 | } 173 | } 174 | } 175 | 176 | protected void parseTitle(XMLStreamReader r) throws XMLStreamException { 177 | this.title = r.getElementText(); 178 | } 179 | 180 | protected void parseCreator(XMLStreamReader r) throws XMLStreamException { 181 | 182 | while (r.hasNext()) { 183 | r.next(); 184 | switch (r.getEventType()) { 185 | case START_ELEMENT: 186 | if (r.getName().equals(AGENT)) { 187 | parseAgent(r); 188 | } 189 | break; 190 | case END_ELEMENT: 191 | if (r.getName().equals(CREATOR)) { 192 | return; 193 | } 194 | break; 195 | } 196 | } 197 | } 198 | 199 | protected void parseAgent(XMLStreamReader r) throws XMLStreamException { 200 | while (r.hasNext()) { 201 | r.next(); 202 | switch (r.getEventType()) { 203 | case START_ELEMENT: 204 | if (r.getName().equals(NAME)) { 205 | parseName(r); 206 | } 207 | break; 208 | case END_ELEMENT: 209 | if (r.getName().equals(AGENT)) { 210 | return; 211 | } 212 | break; 213 | } 214 | } 215 | } 216 | 217 | protected void parseName(XMLStreamReader r) throws XMLStreamException { 218 | this.creator = r.getElementText(); 219 | } 220 | 221 | protected void parseHasFormat(XMLStreamReader r) throws XMLStreamException { 222 | 223 | while (r.hasNext()) { 224 | r.next(); 225 | switch (r.getEventType()) { 226 | case START_ELEMENT: 227 | if (r.getName().equals(FILE)) { 228 | parseFile(r); 229 | } 230 | break; 231 | case END_ELEMENT: 232 | if (r.getName().equals(HAS_FORMAT)) { 233 | return; 234 | } 235 | break; 236 | } 237 | } 238 | } 239 | 240 | protected void parseFile(XMLStreamReader r) throws XMLStreamException { 241 | String url = r.getAttributeValue(RDF, "about"); 242 | 243 | while (r.hasNext()) { 244 | r.next(); 245 | switch (r.getEventType()) { 246 | case START_ELEMENT: 247 | if (r.getName().equals(FORMAT)) { 248 | parseFormat(r); 249 | } 250 | break; 251 | case END_ELEMENT: 252 | if (r.getName().equals(FILE)) { 253 | if (this.format.startsWith("text/html")) { 254 | this.url = url; 255 | } else { 256 | this.format = null; 257 | } 258 | return; 259 | } 260 | break; 261 | } 262 | } 263 | } 264 | 265 | protected void parseFormat(XMLStreamReader r) throws XMLStreamException { 266 | if (this.format != null) { 267 | return; 268 | } 269 | 270 | while (r.hasNext()) { 271 | r.next(); 272 | switch (r.getEventType()) { 273 | case START_ELEMENT: 274 | if (r.getName().equals(DESCRIPTION)) { 275 | parseDescription(r); 276 | } 277 | break; 278 | case END_ELEMENT: 279 | if (r.getName().equals(FORMAT)) { 280 | return; 281 | } 282 | break; 283 | } 284 | } 285 | } 286 | 287 | protected void parseDescription(XMLStreamReader r) throws XMLStreamException { 288 | 289 | while (r.hasNext()) { 290 | r.next(); 291 | switch (r.getEventType()) { 292 | case START_ELEMENT: 293 | if (r.getName().equals(VALUE)) { 294 | parseValue(r); 295 | } 296 | break; 297 | case END_ELEMENT: 298 | if (r.getName().equals(DESCRIPTION)) { 299 | return; 300 | } 301 | break; 302 | } 303 | } 304 | } 305 | 306 | protected void parseValue(XMLStreamReader r) throws XMLStreamException { 307 | this.format = r.getElementText(); 308 | } 309 | 310 | /* 311 | public void process(Path path) throws FileNotFoundException, XMLStreamException { 312 | 313 | XMLInputFactory f = XMLInputFactory.newInstance(); 314 | XMLStreamReader r = f.createXMLStreamReader(new FileInputStream(path.toFile())); 315 | while (r.hasNext()) { 316 | int event = r.next(); 317 | switch (event) { 318 | case XMLStreamReader.START_DOCUMENT: 319 | break; 320 | case XMLStreamReader.END_DOCUMENT: 321 | break; 322 | case XMLStreamReader.START_ELEMENT: 323 | System.err.println( r.getName() ); 324 | if (r.getName().equals(TITLE)) { 325 | parseTitle(r); 326 | break; 327 | } 328 | if (r.getName().equals(HAS_FORMAT)) { 329 | if ( this.format == null ) { 330 | parseHasFormat(r); 331 | } 332 | if ( ! this.format.startsWith( "text/html" ) ) { 333 | this.format = null; 334 | } 335 | break; 336 | } 337 | if (r.getName().equals(CREATOR)) { 338 | parseCreator(r); 339 | } 340 | break; 341 | case XMLStreamReader.END_ELEMENT: 342 | break; 343 | } 344 | 345 | 346 | //if (this.format != null && this.format.startsWith("text/html")) { 347 | //break; 348 | //} 349 | } 350 | 351 | r.close(); 352 | 353 | if (this.url != null) { 354 | System.err.println( "---" ); 355 | System.err.println(" title: " + this.title); 356 | System.err.println(" url: " + this.url); 357 | System.err.println("creator: " + this.creator); 358 | } 359 | } 360 | 361 | protected void parseTitle(XMLStreamReader r) throws XMLStreamException { 362 | this.title = r.getElementText(); 363 | System.err.println("parse title: " + this.title); 364 | } 365 | 366 | protected void parseCreator(XMLStreamReader r) throws XMLStreamException { 367 | 368 | while (r.hasNext()) { 369 | int event = r.next(); 370 | switch (event) { 371 | case XMLStreamReader.START_ELEMENT: 372 | if (r.getNamespaceURI().equals(PG_TERMS) && r.getLocalName().equals("agent")) { 373 | parseAgent(r); 374 | } 375 | } 376 | } 377 | } 378 | 379 | protected void parseAgent(XMLStreamReader r) throws XMLStreamException { 380 | while (r.hasNext()) { 381 | int event = r.next(); 382 | switch (event) { 383 | case XMLStreamReader.START_ELEMENT: 384 | if (r.getNamespaceURI().equals(PG_TERMS) && r.getLocalName().equals("name")) { 385 | this.creator = r.getElementText(); 386 | return; 387 | } 388 | } 389 | } 390 | } 391 | 392 | protected void parseHasFormat(XMLStreamReader r) throws XMLStreamException { 393 | LOOP: 394 | while (r.hasNext()) { 395 | int event = r.next(); 396 | 397 | switch (event) { 398 | case XMLStreamReader.START_ELEMENT: 399 | if (r.getNamespaceURI().equals(PG_TERMS)) { 400 | if (r.getLocalName().equals("file")) { 401 | parseFile(r); 402 | } 403 | } 404 | break; 405 | 406 | case XMLStreamReader.END_ELEMENT: 407 | if (r.getNamespaceURI().equals(DC_TERMS)) { 408 | if (r.getLocalName().equals("hasFormat")) { 409 | return; 410 | } 411 | } 412 | break; 413 | } 414 | } 415 | } 416 | 417 | protected void parseFile(XMLStreamReader r) throws XMLStreamException { 418 | this.url = r.getAttributeValue(RDF, "about"); 419 | this.format = null; 420 | 421 | LOOP: 422 | while (r.hasNext()) { 423 | int event = r.next(); 424 | 425 | switch (event) { 426 | case XMLStreamReader.START_ELEMENT: 427 | if (r.getNamespaceURI().equals(DC_TERMS)) { 428 | if (r.getLocalName().equals("format")) { 429 | parseFormat(r); 430 | break LOOP; 431 | } 432 | } 433 | break; 434 | case XMLStreamReader.END_ELEMENT: 435 | if (r.getNamespaceURI().equals(PG_TERMS) && r.getLocalName().equals("hasFormat")) { 436 | return; 437 | } 438 | } 439 | } 440 | 441 | } 442 | 443 | protected void parseFormat(XMLStreamReader r) throws XMLStreamException { 444 | 445 | while (r.hasNext()) { 446 | int event = r.next(); 447 | 448 | switch (event) { 449 | case XMLStreamReader.START_ELEMENT: 450 | if (r.getNamespaceURI().equals(RDF)) { 451 | if (r.getLocalName().equals("Description")) { 452 | parseDescription(r); 453 | } 454 | } 455 | break; 456 | case XMLStreamReader.END_ELEMENT: 457 | if (r.getNamespaceURI().equals(DC_TERMS) && r.getLocalName().equals("format")) { 458 | return; 459 | } 460 | } 461 | } 462 | } 463 | 464 | protected void parseDescription(XMLStreamReader r) throws XMLStreamException { 465 | if (this.format != null) { 466 | return; 467 | } 468 | 469 | while (r.hasNext()) { 470 | int event = r.next(); 471 | 472 | switch (event) { 473 | case XMLStreamReader.START_ELEMENT: 474 | if (r.getNamespaceURI().equals(RDF)) { 475 | if (r.getLocalName().equals("value")) { 476 | this.format = r.getElementText(); 477 | return; 478 | } 479 | } 480 | break; 481 | case XMLStreamReader.END_ELEMENT: 482 | if (r.getNamespaceURI().equals("RDF") && r.getLocalName().equals("Description")) { 483 | return; 484 | } 485 | } 486 | } 487 | } 488 | */ 489 | } 490 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/ServicesFactory.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | import javax.enterprise.inject.Produces; 5 | 6 | import org.wildfly.swarm.netflix.ribbon.secured.client.SecuredRibbon; 7 | 8 | /** 9 | * @author Bob McWhirter 10 | */ 11 | @ApplicationScoped 12 | public class ServicesFactory { 13 | 14 | @Produces 15 | @ApplicationScoped 16 | public static PricingService getInstance() { 17 | return SecuredRibbon.from(PricingService.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/Store.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | import javax.xml.namespace.QName; 5 | import javax.xml.stream.XMLInputFactory; 6 | import javax.xml.stream.XMLStreamException; 7 | import javax.xml.stream.XMLStreamReader; 8 | import java.io.InputStream; 9 | import java.util.*; 10 | 11 | import static javax.xml.stream.XMLStreamConstants.*; 12 | 13 | /** 14 | * @author Bob McWhirter 15 | */ 16 | @ApplicationScoped 17 | public class Store { 18 | 19 | public static final QName BOOKS = new QName("library"); 20 | public static final QName BOOK = new QName("book"); 21 | public static final QName TITLE = new QName("title"); 22 | public static final QName AUTHOR = new QName("author"); 23 | public static final QName URL = new QName("url"); 24 | 25 | private Map booksById = new HashMap<>(); 26 | private Map> booksByAuthor = new TreeMap<>(); 27 | private Map> booksByTitle = new TreeMap<>(); 28 | 29 | public Store() throws XMLStreamException { 30 | XMLInputFactory f = XMLInputFactory.newFactory(); 31 | InputStream storeStream = Store.class.getClassLoader().getResourceAsStream("store.xml"); 32 | if(null==storeStream) 33 | throw new RuntimeException("Failed to load store.xml"); 34 | 35 | XMLStreamReader r = f.createXMLStreamReader(storeStream); 36 | 37 | while (r.hasNext()) { 38 | r.next(); 39 | switch (r.getEventType()) { 40 | case START_DOCUMENT: 41 | break; 42 | case END_DOCUMENT: 43 | break; 44 | 45 | case START_ELEMENT: 46 | if (r.getName().equals(BOOKS)) { 47 | parseBooks(r); 48 | } 49 | break; 50 | case END_ELEMENT: 51 | break; 52 | 53 | } 54 | } 55 | } 56 | 57 | protected void parseBooks(XMLStreamReader r) throws XMLStreamException { 58 | while (r.hasNext()) { 59 | r.next(); 60 | switch (r.getEventType()) { 61 | case START_ELEMENT: 62 | if (r.getName().equals(BOOK)) { 63 | parseBook(r); 64 | } 65 | break; 66 | case END_ELEMENT: 67 | if ( r.getName().equals(BOOKS) ) { 68 | return; 69 | } 70 | } 71 | } 72 | } 73 | 74 | protected void parseBook(XMLStreamReader r) throws XMLStreamException { 75 | 76 | String id = r.getAttributeValue( null, "id" ); 77 | String title = null; 78 | String author = null; 79 | String url = null; 80 | 81 | while (r.hasNext()) { 82 | r.next(); 83 | switch (r.getEventType()) { 84 | case START_ELEMENT: 85 | if (r.getName().equals(TITLE)) { 86 | title = r.getElementText(); 87 | } 88 | if ( r.getName().equals(AUTHOR) ) { 89 | author = r.getElementText(); 90 | } 91 | if (r.getName().equals(URL ) ) { 92 | url = r.getElementText(); 93 | } 94 | break; 95 | case END_ELEMENT: 96 | if ( r.getName().equals(BOOK) ) { 97 | addBook( id, title, author, url ); 98 | return; 99 | } 100 | } 101 | } 102 | } 103 | 104 | protected void addBook(String id, String title, String author, String url) { 105 | Book book = new Book(id, title, author, url ); 106 | 107 | List list = this.booksByAuthor.get(author); 108 | if ( list == null ) { 109 | list = new ArrayList<>(); 110 | this.booksByAuthor.put( author.toLowerCase(), list ); 111 | } 112 | list.add( book ); 113 | 114 | list = this.booksByTitle.get(title); 115 | if ( list == null ) { 116 | list = new ArrayList<>(); 117 | this.booksByTitle.put( title.toLowerCase(), list ); 118 | } 119 | list.add( book ); 120 | 121 | this.booksById.put( id, book ); 122 | 123 | } 124 | 125 | public Book get(String id) { 126 | return this.booksById.get(id); 127 | } 128 | 129 | public static class SearchResult { 130 | public static SearchResult EMPTY = new SearchResult( Collections.emptyList(), 0, 0 ); 131 | 132 | private final Collection results; 133 | 134 | private final int page; 135 | 136 | private final int numPages; 137 | private String requestUri = "unknown"; 138 | 139 | public SearchResult(Collection results, int page, int numPages) { 140 | this.results = results; 141 | this.page = page; 142 | this.numPages = numPages; 143 | } 144 | 145 | public Collection getResults() { 146 | return this.results; 147 | } 148 | 149 | public int getPage() { 150 | return this.page; 151 | } 152 | 153 | public int getNumberOfPages() { 154 | return this.numPages; 155 | } 156 | 157 | public void setRequestUri(String requestUri) { 158 | this.requestUri = requestUri; 159 | } 160 | 161 | public String getRequestUri() { 162 | return this.requestUri; 163 | } 164 | } 165 | 166 | public SearchResult search(String q, int page) { 167 | int PAGE_SIZE = 20; 168 | Set seen = new HashSet<>(); 169 | 170 | List results = new ArrayList<>(); 171 | 172 | q = q.toLowerCase(); 173 | 174 | List chunk = null; 175 | 176 | for (Map.Entry> each : this.booksByTitle.entrySet()) { 177 | if ( each.getKey().contains(q) ) { 178 | chunk = new ArrayList<>(each.getValue()); 179 | chunk.removeAll( seen ); 180 | results.addAll(chunk); 181 | seen.addAll( chunk ); 182 | } 183 | } 184 | 185 | for (Map.Entry> each : this.booksByAuthor.entrySet()) { 186 | if ( each.getKey().contains(q) ) { 187 | chunk = new ArrayList<>(each.getValue()); 188 | chunk.removeAll(seen); 189 | results.addAll(chunk); 190 | seen.addAll( chunk ); 191 | } 192 | } 193 | 194 | int numPages = ( results.size() / PAGE_SIZE ) + 1; 195 | 196 | List returnedResults; 197 | 198 | int start = page * PAGE_SIZE; 199 | int end = page * PAGE_SIZE + PAGE_SIZE; 200 | 201 | if ( start > results.size() ) { 202 | returnedResults = Collections.emptyList(); 203 | } else if ( end > results.size() ) { 204 | returnedResults = results.subList( start, results.size() ); 205 | } else { 206 | returnedResults = results.subList(start, end); 207 | } 208 | 209 | if ( returnedResults.isEmpty() ) { 210 | return SearchResult.EMPTY; 211 | } 212 | 213 | return new SearchResult(returnedResults, page + 1, numPages ); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /store/src/main/java/org/wildfly/swarm/booker/store/StoreResource.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker.store; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import rx.Observable; 5 | 6 | import javax.inject.Inject; 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.Produces; 10 | import javax.ws.rs.QueryParam; 11 | import javax.ws.rs.container.AsyncResponse; 12 | import javax.ws.rs.container.Suspended; 13 | import javax.ws.rs.core.Context; 14 | import javax.ws.rs.core.MediaType; 15 | import javax.ws.rs.core.UriInfo; 16 | import java.nio.charset.Charset; 17 | 18 | /** 19 | * @author Bob McWhirter 20 | */ 21 | @Path("/") 22 | public class StoreResource { 23 | 24 | @Inject 25 | private Store store; 26 | 27 | @Inject 28 | private PricingService pricingService; 29 | 30 | @Context 31 | private UriInfo uriInfo; 32 | 33 | @GET 34 | @Path("/search") 35 | @Produces(MediaType.APPLICATION_JSON) 36 | public Store.SearchResult search(@QueryParam("q") String q, @QueryParam("page") Integer page) { 37 | if (q == null) { 38 | return Store.SearchResult.EMPTY; 39 | } 40 | if (page == null) { 41 | page = 1; 42 | } 43 | Store.SearchResult results = this.store.search(q, page - 1); 44 | results.setRequestUri(uriInfo.getBaseUri().toString()); 45 | return results; 46 | } 47 | 48 | @GET 49 | @Path("/book") 50 | @Produces(MediaType.APPLICATION_JSON) 51 | public void get(@Suspended final AsyncResponse asyncResponse, @QueryParam("id") String id) { 52 | Book book = this.store.get(id); 53 | Observable obs = pricingService.get(id).observe(); 54 | obs.subscribe( 55 | (result) -> { 56 | int price = Integer.parseInt(result.toString(Charset.defaultCharset())); 57 | book.setPrice(price); 58 | asyncResponse.resume(book); 59 | }, 60 | (err) -> { 61 | asyncResponse.resume(err); 62 | } 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /store/src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /store/src/main/resources/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /store/src/main/resources/keycloak.json: -------------------------------------------------------------------------------- 1 | { 2 | "realm": "booker", 3 | "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm/yXPhm3xlNsYOLQGM4YxqjAe5mjBxKcxJCYyQGzz36DQFO59IclAxaFEsgN3OorVL2W9wLLGUveoV47s8rOdq4+obHiO2C/bgDSVcvg+X8RRAngZDR04iPPdD+cjMdxAYb/WsGjlOKju+U8Pk2o/TnRHNmZgbwE9JDnhGrmFycCgu7oQGk6KDVpsp3zVIsr2qrah0ujBwUbPti8NN4OZBupMzgR3oOjzJ9dhkh9qaQN6SErnRj3lENAh7rbizKEWnIxImgi6m6ogLNWMxNKJlrRbER2LCSegDXhhO3zuBhHb0xZ1P+dO+4LL478SCQStutrGSoO0Yc0GiEVFBIncQIDAQAB", 4 | "auth-server-url": "http://localhost:9090/auth", 5 | "ssl-required": "external", 6 | "resource": "web-client", 7 | "credentials": { 8 | "secret": "deec63e4-d242-4180-b402-80fba0a9187e" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /store/src/test/java/ZipkinTest.java: -------------------------------------------------------------------------------- 1 | import javax.ws.rs.client.WebTarget; 2 | import javax.ws.rs.core.MediaType; 3 | 4 | import org.jboss.resteasy.client.jaxrs.ResteasyClient; 5 | import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; 6 | import org.junit.Ignore; 7 | import org.junit.Test; 8 | import org.wildfly.swarm.jaxrs.btm.zipkin.ClientRequestInterceptor; 9 | import org.wildfly.swarm.jaxrs.btm.zipkin.ClientResponseInterceptor; 10 | 11 | /** 12 | * @author Heiko Braun 13 | * @since 07/10/16 14 | */ 15 | public class ZipkinTest { 16 | 17 | @Test 18 | @Ignore 19 | public void testSpanLogging() { 20 | ResteasyClient client = (ResteasyClient) ResteasyClientBuilder.newClient(); 21 | client.register(ClientRequestInterceptor.class); 22 | client.register(ClientResponseInterceptor.class); 23 | WebTarget target = client.target("http://localhost:8082").path("search"); 24 | 25 | javax.ws.rs.core.Response response = target.request(MediaType.APPLICATION_JSON_TYPE).get(); 26 | System.out.println(response.getStatus()); 27 | System.out.println(response.readEntity(String.class)); 28 | 29 | } 30 | 31 | public static void main(String[] args) { 32 | ZipkinTest zipkinTest = new ZipkinTest(); 33 | 34 | for(int i=0; i<100; i++) { 35 | 36 | zipkinTest.testSpanLogging(); 37 | 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vagrant/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Vagrant 3 | 4 | Vagrant enables the rapid and repeatable creation of virtual machines. 5 | 6 | Use Vagrant if you want to run booker in a pre-canned virtual machine. 7 | This is useful to quickly get demos up and running. 8 | 9 | This requires that you download Vagrant (see https://www.vagrantup.com) and 10 | Virtualbox (see https://www.virtualbox.org/). 11 | 12 | After both are installed, simply change to the booker vagrant sub-directory 13 | and run 'vagrant up'. The installation and setup will take a while since it 14 | is downloading roughly 2.5-3GB of bits (including the Fedora OS, booker code, 15 | maven artifacts, etc). 16 | 17 | # Security 18 | LOL! What security?? To prove that point, here are the login credentials: 19 | 20 | ###Root login 21 | user: root 22 | password: root 23 | 24 | ###Demo login: 25 | user: demo 26 | password: demo 27 | 28 | Changing the demo and root passwords will not have a negative impact on 29 | the demo (unless you forget the password). 30 | 31 | # Running the demo 32 | * After 'vagrant up' is done running, log in as demo (password demo) 33 | * Open a terminal within the virtual machine and type 'run_all.sh' 34 | * Open Firefox (or Chrome) within the virtual machine and go to . 35 | 36 | # Speeding up the vagrant build 37 | WildFly Swarm is so rockin' cool, we know that you really want to improve this 38 | demo and post pull requests. If you want to make modifications to the 39 | image in an iterative fashion (during development), then it helps to use local bits 40 | instead of the setup script downloading them for each iteration. To do this, 41 | after you clone booker (and before you run 'vagrant up'), download the following 42 | bits into the project's vagrant/provisioning/downloads directory and the setup script 43 | will copy the archives instead of downloading them: 44 | * 45 | * 46 | * 47 | * 48 | * .m2 directory containing populated Maven booker project repository 49 | 50 | # JBoss Developer Studio 51 | 52 | If you download jboss-devstudio-8.1.0.GA-installer-standalone.jar 53 | (see ) into the 54 | vagrant/provisioning/downloads directory, then the setup script will also 55 | install JBoss Developer Studio. It will not be automatically downloaded. 56 | Alternatively, once Vagrant has created the virtual machine, you can log 57 | in and install it. 58 | 59 | -------------------------------------------------------------------------------- /vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure(2) do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | #config.vm.box = "base" 16 | config.vm.box = "jclingan/fedora22-desktop" 17 | config.vm.provision "shell", path: "provisioning/setup.sh" 18 | 19 | # Disable automatic box update checking. If you disable this, then 20 | # boxes will only be checked for updates when the user runs 21 | # `vagrant box outdated`. This is not recommended. 22 | # config.vm.box_check_update = false 23 | 24 | # Create a forwarded port mapping which allows access to a specific port 25 | # within the machine from a port on the host machine. In the example below, 26 | # accessing "localhost:8080" will access port 80 on the guest machine. 27 | # config.vm.network "forwarded_port", guest: 80, host: 8080 28 | 29 | # Create a private network, which allows host-only access to the machine 30 | # using a specific IP. 31 | # config.vm.network "private_network", ip: "192.168.33.10" 32 | 33 | # Create a public network, which generally matched to bridged network. 34 | # Bridged networks make the machine appear as another physical device on 35 | # your network. 36 | # config.vm.network "public_network" 37 | 38 | # Share an additional folder to the guest VM. The first argument is 39 | # the path on the host to the actual folder. The second argument is 40 | # the path on the guest to mount the folder. And the optional third 41 | # argument is a set of non-required options. 42 | # config.vm.synced_folder "../data", "/vagrant_data" 43 | 44 | # Provider-specific configuration so you can fine-tune various 45 | # backing providers for Vagrant. These expose provider-specific options. 46 | # Example for VirtualBox: 47 | # 48 | config.vm.provider "virtualbox" do |vb| 49 | # # Display the VirtualBox GUI when booting the machine 50 | vb.gui = true 51 | # 52 | # # Customize the amount of memory on the VM: 53 | vb.memory = "8096" 54 | end 55 | # 56 | # View the documentation for the provider you are using for more 57 | # information on available options. 58 | 59 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies 60 | # such as FTP and Heroku are also available. See the documentation at 61 | # https://docs.vagrantup.com/v2/push/atlas.html for more information. 62 | # config.push.define "atlas" do |push| 63 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" 64 | # end 65 | 66 | # Enable provisioning with a shell script. Additional provisioners such as 67 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 68 | # documentation for more information about their specific syntax and use. 69 | # config.vm.provision "shell", inline: <<-SHELL 70 | # sudo apt-get update 71 | # sudo apt-get install -y apache2 72 | # SHELL 73 | end 74 | -------------------------------------------------------------------------------- /vagrant/bin/run_all.sh: -------------------------------------------------------------------------------- 1 | gnome-terminal \ 2 | --tab \ 3 | --working-directory ${HOME}/apps/keycloak-1.5.0.Final \ 4 | -e ${HOME}/bin/run_keycloak.sh 5 | sleep 10 6 | gnome-terminal \ 7 | --tab \ 8 | --working-directory ${HOME}/apps/logstash-1.5.4 \ 9 | -e ${HOME}/bin/run_logstash.sh 10 | sleep 10 11 | gnome-terminal \ 12 | --tab \ 13 | --working-directory ${HOME}/booker/web-client \ 14 | -e ${HOME}/bin/run_webclient.sh 15 | sleep 10 16 | gnome-terminal \ 17 | --tab \ 18 | --working-directory ${HOME}/booker/store \ 19 | -e ${HOME}/bin/run_store.sh 20 | sleep 5 21 | gnome-terminal \ 22 | --tab \ 23 | --working-directory ${HOME}/booker/pricing \ 24 | -e ${HOME}/bin/run_pricing.sh 25 | sleep 5 26 | gnome-terminal \ 27 | --tab \ 28 | --working-directory ${HOME}/booker/library \ 29 | -e ${HOME}/bin/run_library.sh 30 | 31 | gnome-terminal \ 32 | --tab \ 33 | --working-directory ${HOME}/apps/kibana-4.1.2-linux-x64 \ 34 | -e ${HOME}/bin/run_kibana.sh 35 | 36 | -------------------------------------------------------------------------------- /vagrant/bin/run_keycloak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ${HOME}/apps/keycloak-1.5.0.Final 4 | 5 | ./bin/standalone.sh -Djboss.http.port=9090 6 | -------------------------------------------------------------------------------- /vagrant/bin/run_kibana.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | KIBANA_HOME=${HOME}/apps/kibana-4.1.2-linux-x64 4 | cd ${KIBANA_HOME} 5 | 6 | bin/kibana 7 | -------------------------------------------------------------------------------- /vagrant/bin/run_library.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "*****************************" 4 | echo "*** Library Microservice ****" 5 | echo "*****************************" 6 | 7 | cd ${HOME}/booker/library 8 | 9 | if [ "$1" != "" ]; 10 | then 11 | PORT="-Dswarm.port.offset=${1}" 12 | fi 13 | 14 | java ${PORT} -jar target/*-swarm.jar 15 | 16 | #mvn wildfly-swarm:run 17 | -------------------------------------------------------------------------------- /vagrant/bin/run_logstash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BOOKER_HOME=${HOME}/booker/extra/logstash 4 | cd ${HOME}/apps/logstash-1.5.4 5 | bin/logstash agent -f ${BOOKER_HOME}/logstash-wildfly.conf 6 | -------------------------------------------------------------------------------- /vagrant/bin/run_pricing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "*****************************" 4 | echo "*** Pricing Microservice ****" 5 | echo "*****************************" 6 | 7 | cd ${HOME}/booker/pricing 8 | if [ "$1" != "" ]; 9 | then 10 | PORT="-Dswarm.port.offset=${1}" 11 | fi 12 | 13 | java ${PORT} -jar target/*-swarm.jar 14 | 15 | #mvn wildfly-swarm:run 16 | -------------------------------------------------------------------------------- /vagrant/bin/run_store.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "***************************" 4 | echo "*** Store Microservice ****" 5 | echo "***************************" 6 | 7 | cd ${HOME}/booker/store 8 | 9 | if [ "$1" != "" ]; 10 | then 11 | PORT="-Dswarm.port.offset=${1}" 12 | fi 13 | 14 | java ${PORT} -jar target/*swarm.jar 15 | 16 | #mvn wildfly-swarm:run 17 | -------------------------------------------------------------------------------- /vagrant/bin/run_webclient.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "********************************" 4 | echo "*** Web-Client Microservice ****" 5 | echo "********************************" 6 | 7 | cd ${HOME}/booker/web-client 8 | 9 | if [ "$1" != "" ]; 10 | then 11 | PORT="-Dswarm.port.offset=${1}" 12 | fi 13 | 14 | java ${PORT} -jar target/*-swarm.jar 15 | 16 | #mvn wildfly-swarm:run 17 | -------------------------------------------------------------------------------- /vagrant/bin/stop_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pkill -f openjdk 4 | pkill -f keycloak 5 | pkill -f logstash 6 | pkill -f kibana 7 | pkill -f "\-jar" 8 | -------------------------------------------------------------------------------- /vagrant/provisioning/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /vagrant/provisioning/common.sh 4 | 5 | cd ${DEMO_HOME} 6 | echo "Cloning Booker Project" 7 | git clone https://github.com/wildfly-swarm/booker.git 8 | 9 | cd ${BOOKER_HOME} 10 | 11 | # Enable logstash by removing comment block in pom.xml 12 | sed -i 's///g' ${BOOKER_HOME}/pom.xml 13 | 14 | echo "Building Booker maven project. Could take a while. Downloading the Internet." 15 | ${MAVEN_HOME}/bin/mvn -q clean install 16 | -------------------------------------------------------------------------------- /vagrant/provisioning/common.sh: -------------------------------------------------------------------------------- 1 | DEMO_HOME=/home/demo 2 | APP_HOME=${DEMO_HOME}/apps 3 | BIN_DIR=${DEMO_HOME}/bin 4 | DOWNLOAD_HOME=${DEMO_HOME}/Downloads 5 | BOOKER_HOME=${DEMO_HOME}/booker 6 | VAGRANT_HOME=/vagrant 7 | MAVEN_HOME=${APP_HOME}/apache-maven-3.3.3 8 | 9 | export DEMO_HOME APP_HOME BIN_DIR DOWNLOAD_HOME 10 | export BOOKER_HOME VAGRANT_HOME MAVEN_HOME 11 | -------------------------------------------------------------------------------- /vagrant/provisioning/downloads/InstallConfigRecord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /home/demo/apps/jbdevstudio 7 | 8 | 9 | 10 | jbds 11 | 12 | 13 | 14 | 15 | 16 | 17 | /usr/lib/jvm/java-1.8.0/bin/java 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /vagrant/provisioning/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | . /vagrant/provisioning/common.sh 4 | 5 | SOURCE_BINARIES=/vagrant/provisioning/downloads 6 | export SOURCE_BINARIES 7 | 8 | ################################################################## 9 | # User "demo", password "demo" 10 | # To change password, use "openssl passwd -crypt my_password_here" 11 | ################################################################## 12 | 13 | add_demo_user() 14 | { 15 | useradd \ 16 | -d ${DEMO_HOME} \ 17 | -c "Demo" \ 18 | -G users,wheel \ 19 | -p UbxzHpHunqNPk \ 20 | -m \ 21 | demo 22 | 23 | mkdir -p ${DOWNLOAD_HOME} \ 24 | -p ${APP_HOME} \ 25 | -p ${BIN_DIR} 26 | 27 | cd ${DEMO_HOME} 28 | 29 | # Copy over the run* scripts to run the various services 30 | cp ${VAGRANT_HOME}/bin/* ${DEMO_HOME}/bin 31 | chmod 755 ${DEMO_HOME}/bin/* 32 | } 33 | 34 | ###################################################### 35 | # Download the docker packages to run demo using 36 | # docker containers (not implemented yet) 37 | ###################################################### 38 | 39 | setup_docker() 40 | { 41 | curl -sSL https://get.docker.com/ | sh 42 | usermod -aG docker demo 43 | service docker start 44 | systemctl docker enable 45 | docker pull fedora:22 46 | } 47 | 48 | ###################################################### 49 | # Download required 3rd party packages 50 | ###################################################### 51 | 52 | download_required_packages() 53 | { 54 | if [ -f ${SOURCE_BINARIES}/keycloak-1.5.0.Final.zip ]; 55 | then 56 | echo "Copying Keycloack" 57 | cp ${SOURCE_BINARIES}/keycloak-1.5.0.Final.zip ${DOWNLOAD_HOME} 58 | else 59 | echo "Downloading Keycloack" 60 | wget http://downloads.jboss.org/keycloak/1.5.0.Final/keycloak-1.5.0.Final.zip \ 61 | -q -P ${DOWNLOAD_HOME} 62 | fi 63 | 64 | if [ -f ${SOURCE_BINARIES}/logstash-1.5.4.zip ]; 65 | then 66 | echo "Copying LogStash" 67 | cp ${SOURCE_BINARIES}/logstash-1.5.4.zip ${DOWNLOAD_HOME} 68 | else 69 | echo "Downloading LogStash" 70 | wget https://download.elastic.co/logstash/logstash/logstash-1.5.4.zip \ 71 | -q -P ${DOWNLOAD_HOME} 72 | fi 73 | 74 | if [ -f ${SOURCE_BINARIES}/kibana-4.1.2-linux-x64.tar.gz ]; 75 | then 76 | echo "Copying Kibana" 77 | cp ${SOURCE_BINARIES}/kibana-4.1.2-linux-x64.tar.gz ${DOWNLOAD_HOME} 78 | else 79 | echo "Downloading Kibana" 80 | wget https://download.elastic.co/kibana/kibana/kibana-4.1.2-linux-x64.tar.gz \ 81 | -q -P ${DOWNLOAD_HOME} 82 | fi 83 | 84 | if [ -f ${SOURCE_BINARIES}/apache-maven-3.3.3-bin.zip ]; 85 | then 86 | echo "Copying Maven" 87 | cp ${SOURCE_BINARIES}/apache-maven-3.3.3-bin.zip ${DOWNLOAD_HOME} 88 | else 89 | echo "Downloading Maven" 90 | wget http://mirror.olnevhost.net/pub/apache/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.zip \ 91 | -q -P ${DOWNLOAD_HOME} 92 | fi 93 | 94 | echo "Download JBoss Dev Studio" 95 | if [ -f ${SOURCE_BINARIES}/jboss-devstudio-8.1.0.GA-installer-standalone.jar ]; 96 | then 97 | cp ${SOURCE_BINARIES}/jboss-devstudio-8.1.0.GA-installer-standalone.jar ${DOWNLOAD_HOME} 98 | # else 99 | # wget http://www.jboss.org/download-manager/file/jboss-devstudio-8.1.0.GA-installer-standalone.jar \ 100 | # -q -P ${DOWNLOAD_HOME} 101 | fi 102 | } 103 | 104 | ###################################################### 105 | # Unpackage required 3rd party packages 106 | ###################################################### 107 | 108 | unpackage_third_party_apps() 109 | { 110 | cd ${APP_HOME} 111 | 112 | echo "Uncompressing Keycloak" 113 | tar zxf ${DOWNLOAD_HOME}/kibana-4.1.2-linux-x64.tar.gz 114 | 115 | echo "Unzipping Keycloak" 116 | unzip -q ${DOWNLOAD_HOME}/keycloak-1.5.0.Final.zip 117 | 118 | echo "Unzipping LogStash" 119 | unzip -q ${DOWNLOAD_HOME}/logstash-1.5.4.zip 120 | 121 | echo "Unzipping Maven" 122 | unzip -q ${DOWNLOAD_HOME}/apache-maven-3.3.3-bin.zip 123 | } 124 | 125 | ###################################################### 126 | # Build Booker app 127 | ###################################################### 128 | 129 | build_booker_app() 130 | { 131 | if [ -d ${SOURCE_BINARIES}/.m2 ]; 132 | then 133 | echo "Copying pre-populated maven repository" 134 | cp -r ${SOURCE_BINARIES}/.m2 ${DEMO_HOME} 135 | fi 136 | 137 | sudo chown -R demo:users ${DEMO_HOME} 138 | 139 | echo "Building the booker application using Maven" 140 | chmod 755 /vagrant/provisioning/build.sh 141 | sudo runuser -l demo -g users /vagrant/provisioning/build.sh 142 | } 143 | 144 | setup_demo_shell() 145 | { 146 | cat <> ${DEMO_HOME}/.bashrc 147 | 148 | PATH=\${PATH}:${APP_HOME}/apache-maven-3.3.3/bin 149 | PATH=\${PATH}:${DEMO_HOME}/bin 150 | PATH=\${PATH}:${DEMO_HOME}/apps/jbdevstudio/studio 151 | 152 | set -o vi 153 | EOL 154 | } 155 | 156 | ########################################################## 157 | # Setting up keycloak by pre-populating a booker realm 158 | # Kludgy by running keycloak, pre-populating the realm 159 | # and then killing the process after 30 seconds. Timing 160 | # should be good enough unless host is slow or 161 | # paging a lot. 162 | ########################################################## 163 | 164 | setup_keycloak() 165 | { 166 | echo "Setting up keycloak by pre-populating a booker realm" 167 | cd ${APP_HOME}/key* 168 | bin/standalone.sh \ 169 | -Djboss.http.port=9090 \ 170 | -Dkeycloak.migration.action=import \ 171 | -Dkeycloak.migration.provider=singleFile \ 172 | -Dkeycloak.migration.file=${DEMO_HOME}/booker/extra/keycloak/booker.json& 173 | sleep 30 174 | sudo pkill -f keycloak 175 | sudo chown -R demo:users ${APP_HOME}/key* 176 | } 177 | 178 | install_jdev_studio() { 179 | if [ -f ${SOURCE_BINARIES}/jboss-devstudio-8.1.0.GA-installer-standalone.jar ]; 180 | then 181 | java -jar ${SOURCE_BINARIES}/jboss-devstudio-8.1.0.GA-installer-standalone.jar \ 182 | ${SOURCE_BINARIES}/InstallConfigRecord.xml 183 | fi 184 | } 185 | 186 | ############################################## 187 | # main() 188 | ############################################## 189 | 190 | add_demo_user 191 | 192 | # This happens after build_booker_app logs in as demo and creates .bashrc 193 | setup_demo_shell 194 | 195 | # Docker support not implemented yet 196 | #setup_docker 197 | download_required_packages 198 | unpackage_third_party_apps 199 | build_booker_app 200 | setup_keycloak 201 | 202 | install_jdev_studio 203 | 204 | # The End 205 | 206 | -------------------------------------------------------------------------------- /web-client/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/base-jdk:8 2 | 3 | USER root 4 | RUN yum install -y iproute 5 | ADD launch.sh /usr/bin/launch.sh 6 | RUN chmod +x /usr/bin/launch.sh 7 | RUN mkdir -p /opt/booker 8 | RUN chown jboss:jboss /opt/booker 9 | 10 | USER jboss 11 | ADD booker-web-client-swarm.jar /opt/booker/booker-web-client.jar 12 | 13 | EXPOSE 8080 14 | 15 | ENTRYPOINT /usr/bin/launch.sh 16 | -------------------------------------------------------------------------------- /web-client/docker/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IPADDR=$(ip a s | sed -ne '/127.0.0.1/!{s/^[ \t]*inet[ \t]*\([0-9.]\+\)\/.*$/\1/p}') 4 | 5 | /usr/bin/java -Dswarm.http.port=8080 -Dswarm.bind.address=$IPADDR $SWARM_JVM_ARGS -jar /opt/booker/booker-web-client.jar 6 | -------------------------------------------------------------------------------- /web-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 4.0.0 10 | 11 | 12 | org.wildfly.swarm.booker 13 | booker-parent 14 | 1.0.0.Alpha01-SNAPSHOT 15 | ../ 16 | 17 | 18 | booker-web-client 19 | 20 | Booker: Web Client 21 | Booker: Web Client 22 | 23 | jar 24 | 25 | 26 | 5.5.2 27 | 28 | 29 | 30 | 31 | 32 | org.wildfly.swarm 33 | wildfly-swarm-plugin 34 | 35 | org.wildfly.swarm.booker.Main 36 | 37 | 38 | 39 | 40 | package 41 | 42 | 43 | 44 | 45 | 46 | com.spotify 47 | docker-maven-plugin 48 | 49 | false 50 | booker/${project.artifactId} 51 | 52 | ${project.version} 53 | 54 | ${project.basedir}/docker 55 | 56 | 57 | / 58 | ${project.build.directory} 59 | ${project.build.finalName}-swarm.jar 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.wildfly.swarm 70 | undertow 71 | 72 | 73 | org.wildfly.swarm 74 | topology-webapp 75 | 76 | 77 | org.wildfly.swarm.booker 78 | booker-common 79 | 80 | 81 | org.webjars 82 | foundation 83 | ${version.foundation} 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /web-client/src/main/java/org/wildfly/swarm/booker/Main.java: -------------------------------------------------------------------------------- 1 | package org.wildfly.swarm.booker; 2 | 3 | import org.jboss.shrinkwrap.api.ShrinkWrap; 4 | import org.wildfly.swarm.booker.common.ContainerUtils; 5 | import org.wildfly.swarm.container.Container; 6 | import org.wildfly.swarm.topology.webapp.TopologyProperties; 7 | import org.wildfly.swarm.topology.webapp.TopologyWebAppFraction; 8 | import org.wildfly.swarm.undertow.WARArchive; 9 | 10 | /** 11 | * @author Lance Ball 12 | */ 13 | public class Main { 14 | public static void main(String... args) throws Exception { 15 | Container container = new Container(); 16 | container.fraction(ContainerUtils.loggingFraction()); 17 | 18 | System.setProperty(TopologyProperties.CONTEXT_PATH, "/topology-webapp"); 19 | TopologyWebAppFraction topologyWebAppFraction = new TopologyWebAppFraction(); 20 | topologyWebAppFraction.proxyService("store", "/store-proxy"); 21 | topologyWebAppFraction.proxyService("pricing", "/pricing-proxy"); 22 | topologyWebAppFraction.proxyService("library", "/library-proxy"); 23 | container.fraction(topologyWebAppFraction); 24 | container.start(); 25 | 26 | WARArchive war = ShrinkWrap.create(WARArchive.class); 27 | war.staticContent(); 28 | war.addAllDependencies(); 29 | war.addModule("org.wildfly.swarm.container"); 30 | container.deploy(war); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web-client/src/main/resources/WEB-INF/undertow-handlers.conf: -------------------------------------------------------------------------------- 1 | not equals(%R, '/keycloak.json') and not path-prefix('/system/') and not path-prefix('/js/') and not path-prefix('/css/') and not path-prefix('/webjars') and not path-prefix('/images') and regex('/.*') -> rewrite('/index.jsp') 2 | 3 | equals(%R, '/keycloak.json') -> rewrite('/keycloak.jsp') 4 | -------------------------------------------------------------------------------- /web-client/src/main/resources/css/app.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | #header { 4 | background-color: #ddd; 5 | padding-top: 1em; 6 | border: 1px solid #777; 7 | border-top: none; 8 | } 9 | 10 | #booker-logo { 11 | font-size: 130%; 12 | font-style: italic; 13 | font-weight: bold; 14 | } 15 | 16 | #footer { 17 | font-size: 70%; 18 | color: #999; 19 | border-top: 1px solid #999; 20 | } 21 | 22 | 23 | #header ul { 24 | line-height: 1.5; 25 | } 26 | 27 | #header ul .active { 28 | font-weight: bold; 29 | } 30 | 31 | #welcome { 32 | text-align: center; 33 | } 34 | 35 | .book-title { 36 | font-size: 120%; 37 | font-weight: bold; 38 | } 39 | 40 | .book-author { 41 | font-size: 110%; 42 | } 43 | 44 | .explanation { 45 | width: 30%; 46 | float: right; 47 | } 48 | 49 | .details { 50 | color: #999; 51 | } 52 | -------------------------------------------------------------------------------- /web-client/src/main/resources/images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildfly-swarm-archive/booker/92e3ea900894a3c635d0741d7a56e5d16c9f4934/web-client/src/main/resources/images/diagram.png -------------------------------------------------------------------------------- /web-client/src/main/resources/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildfly-swarm-archive/booker/92e3ea900894a3c635d0741d7a56e5d16c9f4934/web-client/src/main/resources/images/logo-small.png -------------------------------------------------------------------------------- /web-client/src/main/resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildfly-swarm-archive/booker/92e3ea900894a3c635d0741d7a56e5d16c9f4934/web-client/src/main/resources/images/logo.png -------------------------------------------------------------------------------- /web-client/src/main/resources/index.jsp: -------------------------------------------------------------------------------- 1 | <%@page contentType="text/html" 2 | import="org.wildfly.swarm.booker.common.*"%> 3 | 4 | <% 5 | String keycloakUrl = Discoverer.externalKeycloakUrl(request.getServerPort()); 6 | %> 7 | 8 | 9 | 10 | Booker 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/account.js: -------------------------------------------------------------------------------- 1 | Booker.Account = React.createClass({ 2 | mixins: [Reflux.connect(Booker.State.UserInfo,"user")], 3 | getInitialState: function() { 4 | return { 5 | user: { 6 | name: 'Booker User', 7 | } 8 | }; 9 | }, 10 | 11 | render: function() { 12 | return ( 13 |
14 |

Your Account

15 |
16 |
    17 |
  • Name: {this.state.user.name}
  • 18 |
  • Username: {this.state.user.preferred_username}
  • 19 |
20 |
21 |
22 | ); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/app.js: -------------------------------------------------------------------------------- 1 | var Router = ReactRouter, 2 | RouteHandler = Router.RouteHandler, 3 | keycloak; 4 | 5 | Booker = {}; 6 | Booker.State = {}; 7 | Booker.Actions = {}; 8 | 9 | Booker.App = React.createClass({ 10 | 11 | render: function() { 12 | return ( 13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 | ); 21 | } 22 | }); 23 | 24 | Booker.CheckAuth = React.createClass({ 25 | render: function() { 26 | if ( ! keycloak.authenticated ) { 27 | keycloak.login(); 28 | } 29 | return ( 30 | 31 | ); 32 | } 33 | }) 34 | 35 | Booker.Home = React.createClass({ 36 | render: function() { 37 | return ( 38 |
39 |

Welcome to Booker

40 |

41 | Booker is an electronic bookstore powered by WildFly Swarm. 42 |

43 | 44 | 45 |

46 | For more information see the About page 47 |

48 |
49 | ); 50 | } 51 | }); 52 | 53 | Booker.About = React.createClass({ 54 | render: function() { 55 | return ( 56 |
57 |

About Booker!

58 | 59 |

60 | Booker! is an electronic bookstore that demonstrates 61 | how many WildFly Swarm-based microservices can play together. 62 |

63 | 64 |

65 | The services are wired together using WildFly node discovery and 66 | invoked using NetflixOSS Ribbon. 67 |

68 | 69 |

70 | You can view the topology to understand 71 | which services are up (and where) at any point in time. 72 |

73 | 74 |

75 | 76 |

77 | 78 |
79 |
80 |

Keycloak

81 |

82 | Keycloak is an independent authentication and authorization server that 83 | runs apart from any app or microservice. It provides integrations to social-login 84 | and other enterprise backing-stores, such as LDAP and Kerberos. Some of the Booker! 85 | services require authenticated users to operate, some just behave differently if the 86 | user is authenticated or anonymous. 87 |

88 |
89 |
90 | 91 |
92 |
93 | 94 |

Web Client

95 |

96 | The web client is implemented as a single-page app, with most 97 | of the logic occurring within the browser. The client is packaged as 98 | a .war file, and then wrapped with WildFly Swarm. The only 99 | server-side component is the Server-Sent Events async servlet which 100 | provides the topology of the microservices to the single-page app running in the browser. 101 |

102 | 103 |

Library

104 |

105 | The library service is used to track which titles have been purchased 106 | by a user. This service uses JPA and an SQL datasource to track combinations 107 | of user-id and book-id. Since simple identifiers are not very useful, 108 | it subsequently invokes the store service to fill in details about your 109 | library, including the title and author of any book in your library. 110 |

111 |
112 | 113 |
114 |

Store

115 |

116 | The store service provides the ability to search and query the 117 | inventory of books, which is kept in-memory (using data from Project Gutenberg). 118 |

119 | 120 |

Pricing

121 |

122 | The pricing service is used by the store to provide the price of each 123 | book. It uses very simple logic but mostly demonstrates how authentication 124 | information can be propagated from the browser, across another service, and 125 | on to an even further-away service. If authentication information is provided, 126 | the pricing service prices a book at $9, otherwise, it prices it at $10. 127 |

128 |
129 | 130 |
131 | 132 | 133 |
134 | 135 | ); 136 | } 137 | }); 138 | 139 | Booker.Footer = React.createClass({ 140 | render: function() { 141 | return ( 142 | 147 | ) 148 | } 149 | }) 150 | 151 | 152 | Booker.NotFound = React.createClass({ 153 | render: function() { 154 | console.log( "render NotFound" ); 155 | return ( 156 |
Not Found
157 | ); 158 | } 159 | }); 160 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/header.js: -------------------------------------------------------------------------------- 1 | Booker.Actions.UserLoggedIn = Reflux.createAction(); 2 | 3 | Booker.State.UserInfo = Reflux.createStore({ 4 | init: function() { 5 | this.listenTo( Booker.Actions.UserLoggedIn, this.output ); 6 | }, 7 | 8 | output: function(info) { 9 | this.trigger(info); 10 | } 11 | 12 | }); 13 | 14 | Booker.Header = React.createClass({ 15 | render: function() { 16 | return ( 17 | 28 | ); 29 | } 30 | }); 31 | 32 | Booker.MenuHeader = React.createClass({ 33 | render: function() { 34 | return ( 35 |
    36 |
  • About
  • 37 |
  • Account
  • 38 |
  • Your Library
  • 39 |
  • The Store
  • 40 |
41 | ); 42 | } 43 | }); 44 | 45 | Booker.AuthHeader = React.createClass({ 46 | render: function() { 47 | if ( keycloak && keycloak.authenticated ) { 48 | return ( 49 | 50 | ) 51 | } else { 52 | return ( 53 | 54 | ) 55 | } 56 | } 57 | }); 58 | 59 | Booker.Unauthenticated = React.createClass({ 60 | onClick: function() { 61 | keycloak.login(); 62 | return false; 63 | }, 64 | render: function() { 65 | return ( 66 | Login 67 | ) 68 | } 69 | }); 70 | 71 | Booker.Authenticated = React.createClass({ 72 | render: function() { 73 | return ( 74 | 78 | ) 79 | } 80 | }); 81 | 82 | Booker.UserInfo = React.createClass({ 83 | mixins: [Reflux.connect(Booker.State.UserInfo,"info")], 84 | getInitialState: function() { 85 | return { 86 | info: { 87 | name: 'Booker User', 88 | } 89 | }; 90 | }, 91 | 92 | render: function() { 93 | return ( 94 | 95 | {this.state.info.name} 96 | 97 | ) 98 | } 99 | 100 | }) 101 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/library.js: -------------------------------------------------------------------------------- 1 | Booker.Actions.Library = Reflux.createActions({ 2 | "load": {children: ["completed","failed"]} 3 | }); 4 | 5 | Booker.Actions.Library.load.listen( function(term) { 6 | topo.ajax( "library", "/items" ) 7 | .then( function(data) { 8 | Booker.Actions.Library.load.completed(data); 9 | }, function(err) { 10 | console.log( "search failed", err ); 11 | }); 12 | }); 13 | 14 | Booker.State.Library = Reflux.createStore({ 15 | 16 | init: function() { 17 | this.listenTo( Booker.Actions.Library.load.completed, this.output ); 18 | }, 19 | 20 | output: function(results) { 21 | this.trigger(results); 22 | } 23 | }); 24 | 25 | Booker.Library = React.createClass({ 26 | mixins: [Reflux.connect(Booker.State.Library,"items")], 27 | 28 | componentWillMount: function() { 29 | Booker.Actions.Library.load() 30 | }, 31 | 32 | getInitialState: function() { 33 | return { 34 | items: [], 35 | } 36 | }, 37 | 38 | render: function() { 39 | return ( 40 |
41 |

Your Library

42 |
    43 | { 44 | this.state.items.map( function(e) { 45 | return ( 46 | 47 | ); 48 | }) 49 | } 50 |
51 |
52 | ) 53 | } 54 | }); 55 | 56 | Booker.Library.Item = React.createClass({ 57 | render: function() { 58 | return ( 59 |
  • {this.props.item.title}
  • 60 | ); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/pagination.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Booker.Pagination = React.createClass({ 4 | render: function() { 5 | if ( this.props.pagination.numberOfPages == 0 ) { 6 | return (
    ); 7 | } 8 | return ( 9 |
    10 |
    {this.props.pagination.page}
    11 |
    12 | ) 13 | } 14 | }) -------------------------------------------------------------------------------- /web-client/src/main/resources/js/routes.js: -------------------------------------------------------------------------------- 1 | var Router = ReactRouter; 2 | var RouteHandler = Router.RouteHandler; 3 | var DefaultRoute = Router.DefaultRoute; 4 | var Link = Router.Link; 5 | var Route = Router.Route; 6 | var NotFoundRoute = Router.Route; 7 | 8 | (function() { 9 | var routes = ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | 28 | 29 | console.log("Keycloak object", keycloak); 30 | if (keycloak) { 31 | keycloak.init({ onLoad: 'check-sso' }).success( function() { 32 | if ( keycloak.authenticated ) { 33 | console.log("Keycloak authenticated"); 34 | keycloak.loadUserInfo().success( function(info) { 35 | Booker.Actions.UserLoggedIn( info ); 36 | console.log("Keycloak user info", info); 37 | }); 38 | } 39 | Router.run(routes, Router.HistoryLocation, function (Handler) { 40 | React.render(, document.getElementById('app')); 41 | }); 42 | }); 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/store.js: -------------------------------------------------------------------------------- 1 | Booker.Actions.SearchStore = Reflux.createActions({ 2 | "search": {children: ["completed","failed"]} 3 | }); 4 | 5 | Booker.Actions.SearchStore.search.listen( function(term) { 6 | topo.getJSON( "store", "/search", { q: term } ) 7 | .then( function(data) { 8 | Booker.Actions.SearchStore.search.completed(data); 9 | }, function(e) { 10 | console.log( "search failed", e ); 11 | }); 12 | }); 13 | 14 | Booker.State.StoreSearchResults = Reflux.createStore({ 15 | init: function() { 16 | this.listenTo( Booker.Actions.SearchStore.search.completed, this.output ); 17 | }, 18 | 19 | output: function(results) { 20 | console.log( results ); 21 | this.trigger(results); 22 | } 23 | }); 24 | 25 | Booker.Store = React.createClass({ 26 | mixins: [Router.Navigation, Router.State], 27 | 28 | getInitialState: function() { 29 | return { q: '', page: 1 }; 30 | }, 31 | 32 | onSubmit: function(event) { 33 | event.preventDefault(); 34 | var searchTerm = $('#store-search').val(); 35 | this.transitionTo('store', {}, { q: searchTerm } ); 36 | Booker.Actions.SearchStore.search(searchTerm); 37 | }, 38 | 39 | handleChange: function(event) { 40 | this.setState({q: event.target.value}); 41 | }, 42 | 43 | initSearch: function() { 44 | if ( this.getQuery().q != this.state.q ) { 45 | this.setState({q: this.getQuery().q } ); 46 | Booker.Actions.SearchStore.search(this.getQuery().q); 47 | } 48 | }, 49 | 50 | componentWillReceiveProps: function() { 51 | this.initSearch(); 52 | }, 53 | 54 | componentWillMount: function() { 55 | this.initSearch(); 56 | }, 57 | 58 | render: function() { 59 | 60 | return ( 61 |
    62 |

    Search

    63 |
    64 | 65 |
    66 | 67 |
    68 | ); 69 | } 70 | }); 71 | 72 | Booker.SearchResults = React.createClass({ 73 | mixins: [Reflux.connect(Booker.State.StoreSearchResults,"results")], 74 | 75 | getInitialState: function() { 76 | return { 77 | results: { page: 0, numberOfPages: 0, results: [] }, 78 | }; 79 | }, 80 | 81 | render: function() { 82 | return ( 83 |
    84 |
      85 | { 86 | this.state.results.results.map( function(e) { 87 | return ( 88 |
    • {e.title}
    • 89 | ); 90 | }) 91 | } 92 |
    93 | 94 |
    95 | Search results from: {this.state.results.requestUri} 96 |
    97 |
    98 | ); 99 | } 100 | }); 101 | 102 | Booker.StoreItem = React.createClass({ 103 | mixins: [Router.State], 104 | 105 | componentWillMount: function() { 106 | var self = this; 107 | 108 | topo.getJSON( "store", "/book", { id: this.getParams().id } ) 109 | .then( function(data) { 110 | self.setState( data ) 111 | }); 112 | }, 113 | 114 | render: function() { 115 | if ( ! this.state ) { 116 | return (
    ); 117 | } 118 | return ( 119 |
    120 |
    {this.state.title}
    121 |
    {this.state.author}
    122 |
    Price ${this.state.price}
    123 | 124 |
    125 | ); 126 | } 127 | }); 128 | 129 | Booker.StoreItem.Purchase = React.createClass({ 130 | mixins: [Reflux.connect(Booker.State.Library,"items")], 131 | 132 | componentWillMount: function() { 133 | Booker.Actions.Library.load(); 134 | }, 135 | 136 | getInitialState: function() { 137 | return { 138 | items: [] 139 | }; 140 | }, 141 | 142 | purchase: function(event) { 143 | event.preventDefault(); 144 | topo.postJSON( "library", "/items", { bookId: this.props.bookId } ) 145 | .then( function(data) { 146 | Booker.Actions.Library.load(); 147 | }, function(err) { 148 | console.log( "failed to purchase", err ); 149 | }); 150 | }, 151 | 152 | hasPurchased: function() { 153 | var self = this; 154 | return this.state.items.some( function(e) { 155 | return ( e.bookId == self.props.bookId ); 156 | }); 157 | }, 158 | 159 | render: function() { 160 | if ( keycloak && !keycloak.token ) { 161 | return ( 162 |
    Login to Purchase
    163 | ); 164 | } else if ( this.hasPurchased() ) { 165 | return ( 166 |
    Purchased
    167 | ); 168 | } else { 169 | return ( 170 | Purchase Now 171 | ); 172 | } 173 | } 174 | 175 | }); 176 | -------------------------------------------------------------------------------- /web-client/src/main/resources/js/topology.js: -------------------------------------------------------------------------------- 1 | Booker.Actions.Topology = Reflux.createAction(); 2 | 3 | Booker.State.Topology = Reflux.createStore({ 4 | init: function() { 5 | this.topology = {}; 6 | this.listenTo( Booker.Actions.Topology, this.output ); 7 | topo.onTopologyChange(Booker.Actions.Topology); 8 | }, 9 | 10 | output: function(topology) { 11 | console.log( "update topology", topology ); 12 | this.topology = topology; 13 | this.trigger(this.topology); 14 | }, 15 | 16 | services: function() { 17 | return Object.getOwnPropertyNames( this.topology ).sort( function(l,r) { 18 | return l.localeCompare(r); 19 | }); 20 | }, 21 | 22 | servers: function(serviceName) { 23 | return this.topology[serviceName] || []; 24 | } 25 | 26 | }); 27 | 28 | Booker.Topology = React.createClass({ 29 | mixins: [Reflux.connect(Booker.State.Topology,"topology")], 30 | 31 | render: function() { 32 | var services = Booker.State.Topology.services(); 33 | return ( 34 |
    35 |

    Topology

    36 |
    37 |

    The topology is the layout of the services, 38 | and how they connect together. WildFly node discovery 39 | is used to link up the NetflixOSS Ribbon components. 40 |

    41 |

    42 | This topology is then made available to the web-app, 43 | and kept up-to-date using Server-Sent Events (SSE) 44 | to the browser. 45 |

    46 |
    47 | { 48 | services.map( function(e) { 49 | return ( 50 | 51 | ) 52 | }) 53 | } 54 |
    55 | ); 56 | } 57 | }); 58 | 59 | Booker.TopologyService = React.createClass({ 60 | render: function() { 61 | return ( 62 |
    63 |

    {this.props.name}

    64 |
      65 | { 66 | this.props.servers.map(function(e){ 67 | return ( 68 |
    • {e.endpoint} ({e.tags.join(', ')})
    • 69 | ) 70 | }) 71 | } 72 |
    73 |
    74 | 75 | ) 76 | } 77 | }); 78 | -------------------------------------------------------------------------------- /web-client/src/main/resources/keycloak.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="application/json" 2 | import="org.wildfly.swarm.booker.common.*" %> 3 | <% 4 | String keycloakUrl = Discoverer.externalKeycloakUrl(request.getServerPort()); 5 | %> 6 | { 7 | "realm": "booker", 8 | "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm/yXPhm3xlNsYOLQGM4YxqjAe5mjBxKcxJCYyQGzz36DQFO59IclAxaFEsgN3OorVL2W9wLLGUveoV47s8rOdq4+obHiO2C/bgDSVcvg+X8RRAngZDR04iPPdD+cjMdxAYb/WsGjlOKju+U8Pk2o/TnRHNmZgbwE9JDnhGrmFycCgu7oQGk6KDVpsp3zVIsr2qrah0ujBwUbPti8NN4OZBupMzgR3oOjzJ9dhkh9qaQN6SErnRj3lENAh7rbizKEWnIxImgi6m6ogLNWMxNKJlrRbER2LCSegDXhhO3zuBhHb0xZ1P+dO+4LL478SCQStutrGSoO0Yc0GiEVFBIncQIDAQAB", 9 | "auth-server-url": "<%= keycloakUrl %>/auth", 10 | "ssl-required": "external", 11 | "resource": "web-client", 12 | "credentials": { 13 | "secret": "deec63e4-d242-4180-b402-80fba0a9187e" 14 | } 15 | } 16 | --------------------------------------------------------------------------------