--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>de.spring.stomp</groupId>
+ <artifactId>spring-stomp</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>SPRING STOMP</name>
+ <url>http://gumartinm.name</url>
+ <description>
+ Spring Websockets. STOMP protocol.
+ </description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>https://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun</developerConnection>
+ <url>https://git.gumartinm.name/JavaForFun</url>
+ </scm>
+
+ <modules>
+ <module>spring-stomp-bom</module>
+ </modules>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>de.spring.stomp</groupId>
+ <artifactId>spring-stomp</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>spring-stomp-bom</artifactId>
+ <packaging>pom</packaging>
+ <name>SPRING STOMP BOM</name>
+ <url>http://gumartinm.name</url>
+ <description>
+ Spring Websockets. STOMP protocol.
+ </description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>http://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun</developerConnection>
+ <url>http://git.gumartinm.name/JavaForFun</url>
+ </scm>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <spring.version>4.2.5.RELEASE</spring.version>
+ </properties>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <properties>
+ <environment.profile>release</environment.profile>
+ </properties>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ </profile>
+ </profiles>
+
+ <modules>
+ <module>../spring-stomp-server-simple</module>
+ <module>../spring-stomp-server-full</module>
+ </modules>
+
+ <dependencies>
+ <!--
+ 1/3 Required dependency for log4j 2 with slf4j: binding between log4j
+ 2 and slf4j
+ -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <!--
+ 2/3 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin
+ (it is the log4j 2 implementation)
+ -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <!--
+ 3/3 Required dependency for getting rid of commons logging. This is
+ the BRIDGE (no binding) between Jakarta Commons Logging (used by Spring)
+ and whatever I am using for logging (in this case I am using log4j 2)
+ See: http://www.slf4j.org/legacy.html We need exclusions in every dependency using
+ Jakarta Commons Logging (see Spring dependencies below)
+ -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <version>1.7.12</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>cglib</groupId>
+ <artifactId>cglib</artifactId>
+ <version>2.2.2</version>
+ </dependency>
+
+ <!-- Unitary and integration tests -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.0.43-beta</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <version>${spring.version}</version>
+ <!--
+ Required dependency for getting rid of commons logging and use my
+ own logging library (in my case I decided to use log4j 2 under slf4j)
+ -->
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- Required for WebSockets -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-websocket</artifactId>
+ <version>${spring.version}</version>
+ <!--
+ Required dependency for getting rid of commons logging and use my
+ own logging library (in my case I decided to use log4j 2 under slf4j)
+ -->
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- Required for STOMP -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-messaging</artifactId>
+ <version>${spring.version}</version>
+ <!--
+ Required dependency for getting rid of commons logging and use my
+ own logging library (in my case I decided to use log4j 2 under slf4j)
+ -->
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- REST API -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ <version>${spring.version}</version>
+ <!--
+ Required dependency for getting rid of commons logging and use my
+ own logging library (in my case I decided to use log4j 2 under slf4j)
+ -->
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-oxm</artifactId>
+ <version>${spring.version}</version>
+ <!--
+ Required dependency for getting rid of commons logging and use my
+ own logging library (in my case I decided to use log4j 2 under slf4j)
+ -->
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+
+ <!-- Required by spring-webmvc -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>4.0.0-b01</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!--
+ Jackson JSON Processor, required by spring-webmvc. See messageConverters
+ in rest-config.xml
+ -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.6.4</version>
+ </dependency>
+
+ <!--
+ Required by spring-context for using JSR-303. See LocalValidatorFactoryBean
+ in rest-config.xml
+ -->
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>1.1.0.Final</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ <version>5.2.2.Final</version>
+ </dependency>
+
+
+ <!-- Unitary and integration tests -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <version>${spring.version}</version>
+ <scope>test</scope>
+ <!--
+ Required dependency for getting rid of commons logging and use my
+ own logging library (in my case I decided to use log4j 2 under slf4j)
+ -->
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.icegreen</groupId>
+ <artifactId>greenmail</artifactId>
+ <version>1.5.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <build>
+
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.19.1</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.19.1</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Specification-Title>${project.description}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.description}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>**/*IntegrationTest.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <includes>
+ <include>**/*IntegrationTest.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>de.spring.stomp</groupId>
+ <artifactId>spring-stomp-bom</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>spring-stomp-server-full</artifactId>
+ <packaging>war</packaging>
+ <name>SPRING STOMP SERVER. FULL FEATURED BROKER.</name>
+ <url>https://gumartinm.name</url>
+ <description>
+ STOMP with Spring Framework. Full featured broker.
+ </description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>https://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun</developerConnection>
+ <url>https://git.gumartinm.name/JavaForFun</url>
+ </scm>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-oxm</artifactId>
+ </dependency>
+
+ <!-- Required for WebSockets -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-websocket</artifactId>
+ </dependency>
+
+ <!-- Required for STOMP -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-messaging</artifactId>
+ </dependency>
+
+ <!-- Required by spring-webmvc -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!--
+ Jackson JSON Processor, required by spring-webmvc. See messageConverters
+ in rest-config.xml
+ -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+
+
+ <!--
+ Required by spring-context for using JSR-303. See LocalValidatorFactoryBean
+ in rest-config.xml
+ -->
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ </dependency>
+
+
+ <!-- Full-featured broker, TCP connection management. -->
+ <dependency>
+ <groupId>io.projectreactor</groupId>
+ <artifactId>reactor-net</artifactId>
+ <version>2.0.8.RELEASE</version>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-all</artifactId>
+ <version>4.0.36.Final</version>
+ </dependency>
+
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/webapp</directory>
+ <excludes>
+ <exclude>**/*.*</exclude>
+ </excludes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources/</directory>
+ <includes>
+ <include>**/*.*</include>
+ </includes>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <webResources>
+ <resource>
+ <filtering>true</filtering>
+ <directory>src/main/webapp</directory>
+ <includes>
+ <include>WEB-INF/web.xml</include>
+ </includes>
+ </resource>
+ </webResources>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+package de.spring.stomp.controllers;
+
+import java.time.LocalDateTime;
+
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class MessageGreetingController {
+
+ // Sending data to /app/greeting from STOMP client (client must first connect to endpoint, in my case portfolio)
+ // connecting to this URL -> http://172.17.0.3/spring-stomp-server/portfolio
+ // sending data to /app/greeting
+
+ // The data sent to /app/greeting will be retrieved by this method.
+ @MessageMapping("/greeting")
+ @SendTo("/topic/greeting")
+ public String handle(String greeting) {
+ // STOMP clients subscribed to /topic/greeting will receive the returned data from this method.
+ // Destination is selected based on a convention but can be overridden via @SendTo
+ // I will be using @SendTo. In my case, it is not required (because it is the same as the destination selected
+ // based on the convention) but I will be using it just for fun.
+ return "[" + LocalDateTime.now() + "]: " + greeting;
+ }
+}
--- /dev/null
+package de.spring.stomp.controllers;
+
+import java.security.Principal;
+import java.time.LocalDateTime;
+
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.simp.annotation.SendToUser;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class UserMessageTradeController {
+
+ @MessageMapping("/trade")
+ @SendToUser(destinations="/topic/position-updates", broadcast=false /* No idea what is this for */)
+ public String executeTrade(String trade, Principal principal) {
+
+ return "[" + LocalDateTime.now() + "]: " + trade;
+ }
+}
--- /dev/null
+package de.spring.stomp.handlers;
+
+import java.security.Principal;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
+
+/**
+ * In some cases it may be useful to assign an identity to a WebSocket session even when
+ * the user has not been formally authenticated. For example, a mobile app might assign some
+ * identity to anonymous users, perhaps based on geographical location. The do that currently,
+ * an application can sub-class DefaultHandshakeHandler and override the determineUser method.
+ * The custom handshake handler can then be plugged in (see examples in
+ * Section 25.2.4, “Deployment Considerations”)
+ */
+public class CustomHandshakeHandler extends DefaultHandshakeHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomHandshakeHandler.class);
+
+ @Override
+ protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
+ Map<String, Object> attributes) {
+
+ Principal principal = request.getPrincipal();
+ if (principal != null) {
+ LOGGER.info("CustomHandshakeHandler: " + principal.getName());
+ }
+
+ return super.determineUser(request, wsHandler, attributes);
+ }
+
+}
--- /dev/null
+package de.spring.stomp.interceptors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
+import org.springframework.messaging.support.ChannelInterceptorAdapter;
+
+public class CustomChannelInterceptor extends ChannelInterceptorAdapter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomChannelInterceptor.class);
+
+ @Override
+ public Message<?> preSend(Message<?> message, MessageChannel channel) {
+ StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
+ StompCommand command = accessor.getCommand();
+
+ LOGGER.info("CustomChannelInterceptor preSend, StompCommand: " + command);
+ LOGGER.info("CustomChannelInterceptor preSend, login: " + accessor.getLogin());
+ LOGGER.info("CustomChannelInterceptor preSend, heartBeat: " + accessor.getHeartbeat());
+ LOGGER.info("CustomChannelInterceptor preSend, destination: " + accessor.getDestination());
+ LOGGER.info("CustomChannelInterceptor preSend, host: " + accessor.getHost());
+
+ return message;
+ }
+
+ @Override
+ public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
+ StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
+ StompCommand command = accessor.getCommand();
+
+ LOGGER.info("CustomChannelInterceptor postSend, StompCommand: " + command);
+ LOGGER.info("CustomChannelInterceptor postSend, login: " + accessor.getLogin());
+ LOGGER.info("CustomChannelInterceptor postSend, heartBeat: " + accessor.getHeartbeat());
+ LOGGER.info("CustomChannelInterceptor postSend, destination: " + accessor.getDestination());
+ LOGGER.info("CustomChannelInterceptor postSend, host: " + accessor.getHost());
+
+ }
+}
--- /dev/null
+package de.spring.stomp.interceptors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
+
+public class CustomHttpHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpHandshakeInterceptor.class);
+
+
+ @Override
+ public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
+ WebSocketHandler wsHandler, Exception ex) {
+ super.afterHandshake(request, response, wsHandler, ex);
+
+ LOGGER.info("Request URI:" + request.getURI());
+ LOGGER.info("Request remote address:" + request.getRemoteAddress());
+ LOGGER.info("Request local address:" + request.getLocalAddress());
+ LOGGER.info("Request headers:" + request.getHeaders());
+
+ LOGGER.info("Response headers:" + response.getHeaders());
+ }
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
+
+public class BrokerAvailabilityListener implements ApplicationListener<BrokerAvailabilityEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BrokerAvailabilityListener.class);
+
+ @Override
+ public void onApplicationEvent(BrokerAvailabilityEvent event) {
+
+ LOGGER.info("BrokerAvailabilityEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("BrokerAvailabilityEvent brokerAvailable: " + event.isBrokerAvailable());
+ LOGGER.info("BrokerAvailabilityEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionConnectEvent;
+
+public class SessionConnectListener implements ApplicationListener<SessionConnectEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionConnectListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionConnectEvent event) {
+ LOGGER.info("SessionConnectEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionConnectEvent user: " + event.getUser());
+ LOGGER.info("SessionConnectEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionConnectedEvent;
+
+public class SessionConnectedListener implements ApplicationListener<SessionConnectedEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionConnectedListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionConnectedEvent event) {
+ LOGGER.info("SessionConnectedEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionConnectedEvent user: " + event.getUser());
+ LOGGER.info("SessionConnectedEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionDisconnectEvent;
+
+public class SessionDisconnectListener implements ApplicationListener<SessionDisconnectEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionUnsubscribeListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionDisconnectEvent event) {
+ LOGGER.info("SessionDisconnectEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionDisconnectEvent user: " + event.getUser());
+ LOGGER.info("SessionDisconnectEvent sessionId: " + event.getSessionId());
+ LOGGER.info("SessionDisconnectEvent close status: " + event.getCloseStatus());
+ LOGGER.info("SessionDisconnectEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionSubscribeEvent;
+
+public class SessionSubscribeListener implements ApplicationListener<SessionSubscribeEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionSubscribeListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionSubscribeEvent event) {
+ LOGGER.info("SessionSubscribeEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionSubscribeEvent user: " + event.getUser());
+ LOGGER.info("SessionSubscribeEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;
+
+public class SessionUnsubscribeListener implements ApplicationListener<SessionUnsubscribeEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionUnsubscribeListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionUnsubscribeEvent event) {
+ LOGGER.info("SessionUnsubscribeEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionUnsubscribeEvent user: " + event.getUser());
+ LOGGER.info("SessionUnsubscribeEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.services;
+
+public interface RestGreetingService {
+
+ void doGreetings(String greeting);
+}
--- /dev/null
+package de.spring.stomp.services;
+
+public interface UserTradeService {
+
+ void doTrade(String user);
+}
--- /dev/null
+package de.spring.stomp.services.impl;
+
+import java.time.LocalDateTime;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
+import org.springframework.stereotype.Service;
+
+import de.spring.stomp.services.RestGreetingService;
+
+@Service("restGreetingService")
+public class RestGreetingServiceImpl
+ implements RestGreetingService, ApplicationListener<BrokerAvailabilityEvent> {
+ private final SimpMessagingTemplate template;
+
+ private volatile boolean isBrokerAvailable = true;
+
+ @Autowired
+ public RestGreetingServiceImpl(SimpMessagingTemplate template) {
+ this.template = template;
+ }
+
+ @Override
+ public void doGreetings(String greeting) {
+ String text = "[" + LocalDateTime.now() + "]:" + greeting;
+
+ if (isBrokerAvailable) {
+ // STOMP clients subscribed to /topic/greeting will receive the data sent by the convertAndSend method.
+ template.convertAndSend("/topic/greeting", text);
+ }
+ }
+
+ @Override
+ public void onApplicationEvent(BrokerAvailabilityEvent event) {
+ // Components using the SimpMessagingTemplate should subscribe to this event
+ // and avoid sending messages at times when the broker is not available.
+ // In any case they should be prepared to handle MessageDeliveryException
+ // when sending a message.
+ isBrokerAvailable = event.isBrokerAvailable();
+ }
+}
--- /dev/null
+package de.spring.stomp.services.impl;
+
+import java.time.LocalDateTime;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
+import org.springframework.stereotype.Service;
+
+import de.spring.stomp.services.UserTradeService;
+
+@Service("userTradeService")
+public class UserTradeServiceImpl
+ implements UserTradeService, ApplicationListener<BrokerAvailabilityEvent> {
+ private final SimpMessagingTemplate template;
+
+ private volatile boolean isBrokerAvailable = true;
+
+ @Autowired
+ public UserTradeServiceImpl(SimpMessagingTemplate template) {
+ this.template = template;
+ }
+
+ @Override
+ public void doTrade(String user) {
+ String text = "[" + LocalDateTime.now() + "]:" + user;
+
+ if (isBrokerAvailable) {
+ // STOMP clients subscribed to /topic/position-updates will receive the data sent by the convertAndSend method.
+ template.convertAndSendToUser(user, "/topic/position-updates", text);
+ }
+ }
+
+ @Override
+ public void onApplicationEvent(BrokerAvailabilityEvent event) {
+ // Components using the SimpMessagingTemplate should subscribe to this event
+ // and avoid sending messages at times when the broker is not available.
+ // In any case they should be prepared to handle MessageDeliveryException
+ // when sending a message.
+ isBrokerAvailable = event.isBrokerAvailable();
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.spring.stomp.services.RestGreetingService;
+
+
+@RestController
+public class RestGreetingController {
+ private final RestGreetingService restGreetingService;
+
+ @Autowired
+ public RestGreetingController(RestGreetingService restGreetingService) {
+ this.restGreetingService = restGreetingService;
+ }
+
+ // Sending data to /topic/greeting from REST service.
+ // POST http://localhost:8080/spring-stomp-server/greetings
+
+ @RequestMapping(path="/greetings", method=RequestMethod.POST)
+ public void handle(@RequestBody String greeting) {
+
+ // STOMP clients subscribed to /topic/greeting will receive the data sent by the convertAndSend method.
+ restGreetingService.doGreetings(greeting);
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.controller;
+
+import java.time.LocalDateTime;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import de.spring.stomp.services.UserTradeService;
+
+public class UserTradeController {
+ private final UserTradeService userTradeService;
+
+ @Autowired
+ public UserTradeController(UserTradeService userTradeService) {
+ this.userTradeService = userTradeService;
+ }
+
+ // Sending data to /topic/greeting from REST service.
+ // POST http://localhost:8080/spring-stomp-server/trade
+
+ @RequestMapping(path="/trade", method=RequestMethod.POST)
+ public void handle(@RequestBody String user) {
+ userTradeService.doTrade(user);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ status: The level of internal Log4j events that should be logged to the console.
+ Valid values for this attribute are "trace", "debug", "info", "warn", "error" and "fatal".
+
+ monitorInterval: The minimum amount of time, in seconds, that must elapse before the file configuration is checked for changes.
+
+
+ see https://logging.apache.org/log4j/2.x/manual/configuration.html
+ -->
+<Configuration status="error" strict="true" monitorInterval="30"
+ name="XMLConfigTest" packages="org.apache.logging.log4j.test">
+
+ <!--
+ ALL > TRACE > DEBUG > INFO > WARN > ERROR > OFF
+
+ ERROR by default.
+ -->
+
+ <Appenders>
+ <Appender type="Console" name="STDOUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
+ </Appender>
+ </Appenders>
+ <Loggers>
+
+ <!--
+ SockJS client disconnects. Requires TRACE level always.
+ It works because I know for sure I will be using WebSockets when using SockJS.
+ -->
+ <Logger
+ name="org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession"
+ level="TRACE" additivity="false">
+ <AppenderRef ref="STDOUT" />
+ </Logger>
+
+ <!--
+ General logging Spring.
+ -->
+ <Logger name="org.springframework" level="INFO" additivity="false">
+ <AppenderRef ref="STDOUT" />
+ </Logger>
+
+
+ <!--
+ Anything else will be using TRACE logging level.
+ -->
+ <Root level="INFO">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+</Configuration>
--- /dev/null
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/mvc
+ http://www.springframework.org/schema/mvc/spring-mvc.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <!--
+ I am declaring my beans without the automatic annotation. :/
+ Better because we are saving memory but it requires more configuration.
+
+ See: org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
+ <mvc:annotation-driven/>
+ -->
+
+
+ <context:annotation-config />
+
+ <context:component-scan base-package="de.spring.webservices.rest"/>
+
+ <!--
+ Required beans for generating XML responses from Java objects using JAXB annotations
+ Jackson also works but it doesn't generate XML with namespaces... O.o
+
+ This implementation will be slower than the one using Jackson :( but I am going to use it just for WADL generation :)
+ -->
+ <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
+ <property name="packagesToScan" value="org.jvnet.ws.wadl"/>
+ </bean>
+ <bean id="jaxbConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
+ <constructor-arg ref="jaxbMarshaller" />
+ </bean>
+
+ <!-- Required beans for generating JSON responses from Java objects -->
+ <bean id="jsonObjectMapperFactory" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
+ p:indentOutput="true" p:failOnEmptyBeans="false">
+ <property name="featuresToDisable">
+ <array>
+ <util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES"/>
+ <util:constant static-field="com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION"/>
+ </array>
+ </property>
+ </bean>
+
+ <util:list id="messageConverters">
+ <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" p:objectMapper-ref="jsonObjectMapperFactory"/>
+ <ref bean="jaxbConverter" />
+ <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
+ </util:list>
+
+
+ <bean name="handlerAdapter"
+ class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
+ <property name="webBindingInitializer">
+ <bean
+ class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
+ <!-- It enables us to use JSR-303 -->
+ <property name="validator">
+ <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
+ </property>
+ </bean>
+ </property>
+ <property name="messageConverters" ref="messageConverters" />
+
+
+ <property name="requestBodyAdvice">
+ <util:list>
+ <bean id="requestBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice"/>
+ </util:list>
+ </property>
+
+
+ <property name="responseBodyAdvice">
+ <util:list>
+ <bean id="responseBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice"/>
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
+
+ <mvc:default-servlet-handler />
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:websocket="http://www.springframework.org/schema/websocket"
+
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/websocket
+ http://www.springframework.org/schema/websocket/spring-websocket.xsd">
+
+ <!--
+ Searches for beans in packages (instead of XML configuration we can use
+ in this way annotations like @Service, @Endpoint, etc, etc)
+ -->
+ <context:component-scan base-package="de.spring.stomp"/>
+
+
+ <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
+ <property name="maxTextMessageBufferSize" value="8192"/>
+ <property name="maxBinaryMessageBufferSize" value="8192"/>
+ </bean>
+
+ <!--
+ Note that even though the STOMP CONNECT frame has "login" and "passcode" headers that can be used for
+ authentication, Spring’s STOMP WebSocket support ignores them and currently expects users to have been
+ authenticated already via HTTP.
+
+ In some cases it may be useful to assign an identity to a WebSocket session even when the user has not
+ been formally authenticated. For example, a mobile app might assign some identity to anonymous users,
+ perhaps based on geographical location. The do that currently, an application can sub-class DefaultHandshakeHandler
+ and override the determineUser method. The custom handshake handler can then be plugged in
+ (see examples in Section 25.2.4, “Deployment Considerations”).
+ -->
+ <bean id="customHandshakeHandler" class="de.spring.stomp.handlers.CustomHandshakeHandler"/>
+
+ <!-- Interceptors -->
+ <bean id="customChannelInterceptor" class="de.spring.stomp.interceptors.CustomChannelInterceptor"/>
+ <bean id="customHttpHandshakeInterceptor" class="de.spring.stomp.interceptors.CustomHttpHandshakeInterceptor"/>
+
+ <!-- Listeners -->
+ <bean id="brokerAvailabilityListener" class="de.spring.stomp.listeners.BrokerAvailabilityListener"/>
+ <bean id="sessionConnectedListener" class="de.spring.stomp.listeners.SessionConnectedListener"/>
+ <bean id="sessionConnectListener" class="de.spring.stomp.listeners.SessionConnectListener"/>
+ <bean id="sessionDisconnectListener" class="de.spring.stomp.listeners.SessionDisconnectListener"/>
+ <bean id="sessionSubscribeListener" class="de.spring.stomp.listeners.SessionSubscribeListener"/>
+ <bean id="sessionUnsubscribeListener" class="de.spring.stomp.listeners.SessionUnsubscribeListener"/>
+
+ <!-- STOMP -->
+ <!-- Full-featured broker -->
+ <websocket:message-broker application-destination-prefix="/app">
+ <websocket:transport send-timeout="15000" send-buffer-size="524288" message-size="131072" />
+ <websocket:stomp-endpoint path="/fullportfolio" allowed-origins="*">
+ <websocket:handshake-handler ref="customHandshakeHandler" />
+ <websocket:handshake-interceptors>
+ <ref bean="customHttpHandshakeInterceptor"/>
+ </websocket:handshake-interceptors>
+ <websocket:sockjs/>
+ </websocket:stomp-endpoint>
+ <!--
+ Full-featured broker
+ -->
+ <websocket:stomp-broker-relay prefix="/topic, /queue"
+ relay-host="localhost" relay-port="61612" auto-startup="true"
+ heartbeat-send-interval="20000" heartbeat-receive-interval="20000" />
+ <!--
+ <websocket:stomp-broker-relay prefix="/topic, /queue"
+ client-login="" system-login="" client-passcode="" system-passcode=""
+ virtual-host=""/>
+ -->
+
+ <websocket:client-inbound-channel>
+ <websocket:executor core-pool-size="100" max-pool-size="200" keep-alive-seconds="600"/>
+ <websocket:interceptors>
+ <ref bean="customChannelInterceptor"/>
+ </websocket:interceptors>
+ </websocket:client-inbound-channel>
+ <websocket:client-outbound-channel>
+ <websocket:executor core-pool-size="101" max-pool-size="201" keep-alive-seconds="601"/>
+ <websocket:interceptors>
+ <ref bean="customChannelInterceptor"/>
+ </websocket:interceptors>
+ </websocket:client-outbound-channel>
+ </websocket:message-broker>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="2.4">
+
+ <display-name>Spring STOMP: example full featured broker</display-name>
+
+ <listener>
+ <listener-class>
+ org.springframework.web.context.ContextLoaderListener
+ </listener-class>
+ </listener>
+
+ <context-param>
+ <param-name>spring.profiles.default</param-name>
+ <param-value>${environment.profile}</param-value>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>
+ classpath*:spring-configuration/*.xml
+ </param-value>
+ </context-param>
+
+ <!-- Spring REST support -->
+ <servlet>
+ <servlet-name>spring-rest</servlet-name>
+ <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ <init-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>classpath*:spring-configuration/mvc/rest/*.xml</param-value>
+ </init-param>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>spring-rest</servlet-name>
+ <!-- REQUIRED PATTERN BY swagger-ui. IT DOESN'T WORK WITH ANY OTHER o.O -->
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>de.spring.stomp</groupId>
+ <artifactId>spring-stomp-bom</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>spring-stomp-server-simple</artifactId>
+ <packaging>war</packaging>
+ <name>SPRING STOMP SERVER. SIMPLE BROKER.</name>
+ <url>https://gumartinm.name</url>
+ <description>
+ STOMP with Spring Framework. Simple broker.
+ </description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>https://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun</developerConnection>
+ <url>https://git.gumartinm.name/JavaForFun</url>
+ </scm>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-oxm</artifactId>
+ </dependency>
+
+ <!-- Required for WebSockets -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-websocket</artifactId>
+ </dependency>
+
+ <!-- Required for STOMP -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-messaging</artifactId>
+ </dependency>
+
+ <!-- Required by spring-webmvc -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!--
+ Jackson JSON Processor, required by spring-webmvc. See messageConverters
+ in rest-config.xml
+ -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+
+
+ <!--
+ Required by spring-context for using JSR-303. See LocalValidatorFactoryBean
+ in rest-config.xml
+ -->
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ </dependency>
+
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/webapp</directory>
+ <excludes>
+ <exclude>**/*.*</exclude>
+ </excludes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources/</directory>
+ <includes>
+ <include>**/*.*</include>
+ </includes>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <webResources>
+ <resource>
+ <filtering>true</filtering>
+ <directory>src/main/webapp</directory>
+ <includes>
+ <include>WEB-INF/web.xml</include>
+ </includes>
+ </resource>
+ </webResources>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+package de.spring.stomp.controllers;
+
+import java.time.LocalDateTime;
+
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class MessageGreetingController {
+
+ // Sending data to /app/greeting from STOMP client (client must first connect to endpoint, in my case portfolio)
+ // connecting to this URL -> http://172.17.0.3/spring-stomp-server/portfolio
+ // sending data to /app/greeting
+
+ // The data sent to /app/greeting will be retrieved by this method.
+ @MessageMapping("/greeting")
+ @SendTo("/topic/greeting")
+ public String handle(String greeting) {
+ // STOMP clients subscribed to /topic/greeting will receive the returned data from this method.
+ // Destination is selected based on a convention but can be overridden via @SendTo
+ // I will be using @SendTo. In my case, it is not required (because it is the same as the destination selected
+ // based on the convention) but I will be using it just for fun.
+ return "[" + LocalDateTime.now() + "]: " + greeting;
+ }
+}
--- /dev/null
+package de.spring.stomp.controllers;
+
+import java.security.Principal;
+import java.time.LocalDateTime;
+
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.simp.annotation.SendToUser;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class UserMessageTradeController {
+
+ @MessageMapping("/trade")
+ @SendToUser(destinations="/topic/position-updates", broadcast=false /* No idea what is this for */)
+ public String executeTrade(String trade, Principal principal) {
+
+ return "[" + LocalDateTime.now() + "]: " + trade;
+ }
+}
--- /dev/null
+package de.spring.stomp.handlers;
+
+import java.security.Principal;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
+
+/**
+ * In some cases it may be useful to assign an identity to a WebSocket session even when
+ * the user has not been formally authenticated. For example, a mobile app might assign some
+ * identity to anonymous users, perhaps based on geographical location. The do that currently,
+ * an application can sub-class DefaultHandshakeHandler and override the determineUser method.
+ * The custom handshake handler can then be plugged in (see examples in
+ * Section 25.2.4, “Deployment Considerations”)
+ */
+public class CustomHandshakeHandler extends DefaultHandshakeHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomHandshakeHandler.class);
+
+ @Override
+ protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
+ Map<String, Object> attributes) {
+
+ Principal principal = request.getPrincipal();
+ if (principal != null) {
+ LOGGER.info("CustomHandshakeHandler: " + principal.getName());
+ }
+
+ return super.determineUser(request, wsHandler, attributes);
+ }
+
+}
--- /dev/null
+package de.spring.stomp.interceptors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
+import org.springframework.messaging.support.ChannelInterceptorAdapter;
+
+public class CustomChannelInterceptor extends ChannelInterceptorAdapter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomChannelInterceptor.class);
+
+ @Override
+ public Message<?> preSend(Message<?> message, MessageChannel channel) {
+ StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
+ StompCommand command = accessor.getCommand();
+
+ LOGGER.info("CustomChannelInterceptor preSend, StompCommand: " + command);
+ LOGGER.info("CustomChannelInterceptor preSend, login: " + accessor.getLogin());
+ LOGGER.info("CustomChannelInterceptor preSend, heartBeat: " + accessor.getHeartbeat());
+ LOGGER.info("CustomChannelInterceptor preSend, destination: " + accessor.getDestination());
+ LOGGER.info("CustomChannelInterceptor preSend, host: " + accessor.getHost());
+
+ return message;
+ }
+
+ @Override
+ public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
+ StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
+ StompCommand command = accessor.getCommand();
+
+ LOGGER.info("CustomChannelInterceptor postSend, StompCommand: " + command);
+ LOGGER.info("CustomChannelInterceptor postSend, login: " + accessor.getLogin());
+ LOGGER.info("CustomChannelInterceptor postSend, heartBeat: " + accessor.getHeartbeat());
+ LOGGER.info("CustomChannelInterceptor postSend, destination: " + accessor.getDestination());
+ LOGGER.info("CustomChannelInterceptor postSend, host: " + accessor.getHost());
+
+ }
+}
--- /dev/null
+package de.spring.stomp.interceptors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
+
+public class CustomHttpHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpHandshakeInterceptor.class);
+
+
+ @Override
+ public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
+ WebSocketHandler wsHandler, Exception ex) {
+ super.afterHandshake(request, response, wsHandler, ex);
+
+ LOGGER.info("Request URI:" + request.getURI());
+ LOGGER.info("Request remote address:" + request.getRemoteAddress());
+ LOGGER.info("Request local address:" + request.getLocalAddress());
+ LOGGER.info("Request headers:" + request.getHeaders());
+
+ LOGGER.info("Response headers:" + response.getHeaders());
+ }
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
+
+public class BrokerAvailabilityListener implements ApplicationListener<BrokerAvailabilityEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BrokerAvailabilityListener.class);
+
+ @Override
+ public void onApplicationEvent(BrokerAvailabilityEvent event) {
+
+ LOGGER.info("BrokerAvailabilityEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("BrokerAvailabilityEvent brokerAvailable: " + event.isBrokerAvailable());
+ LOGGER.info("BrokerAvailabilityEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionConnectEvent;
+
+public class SessionConnectListener implements ApplicationListener<SessionConnectEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionConnectListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionConnectEvent event) {
+ LOGGER.info("SessionConnectEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionConnectEvent user: " + event.getUser());
+ LOGGER.info("SessionConnectEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionConnectedEvent;
+
+public class SessionConnectedListener implements ApplicationListener<SessionConnectedEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionConnectedListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionConnectedEvent event) {
+ LOGGER.info("SessionConnectedEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionConnectedEvent user: " + event.getUser());
+ LOGGER.info("SessionConnectedEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionDisconnectEvent;
+
+public class SessionDisconnectListener implements ApplicationListener<SessionDisconnectEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionUnsubscribeListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionDisconnectEvent event) {
+ LOGGER.info("SessionDisconnectEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionDisconnectEvent user: " + event.getUser());
+ LOGGER.info("SessionDisconnectEvent sessionId: " + event.getSessionId());
+ LOGGER.info("SessionDisconnectEvent close status: " + event.getCloseStatus());
+ LOGGER.info("SessionDisconnectEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionSubscribeEvent;
+
+public class SessionSubscribeListener implements ApplicationListener<SessionSubscribeEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionSubscribeListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionSubscribeEvent event) {
+ LOGGER.info("SessionSubscribeEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionSubscribeEvent user: " + event.getUser());
+ LOGGER.info("SessionSubscribeEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.listeners;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;
+
+public class SessionUnsubscribeListener implements ApplicationListener<SessionUnsubscribeEvent> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionUnsubscribeListener.class);
+
+ @Override
+ public void onApplicationEvent(SessionUnsubscribeEvent event) {
+ LOGGER.info("SessionUnsubscribeEvent timestamp: " + event.getTimestamp());
+ LOGGER.info("SessionUnsubscribeEvent user: " + event.getUser());
+ LOGGER.info("SessionUnsubscribeEvent: " + event.toString());
+ }
+
+}
--- /dev/null
+package de.spring.stomp.services;
+
+public interface RestGreetingService {
+
+ void doGreetings(String greeting);
+}
--- /dev/null
+package de.spring.stomp.services;
+
+public interface UserTradeService {
+
+ void doTrade(String user);
+}
--- /dev/null
+package de.spring.stomp.services.impl;
+
+import java.time.LocalDateTime;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
+import org.springframework.stereotype.Service;
+
+import de.spring.stomp.services.RestGreetingService;
+
+@Service("restGreetingService")
+public class RestGreetingServiceImpl
+ implements RestGreetingService, ApplicationListener<BrokerAvailabilityEvent> {
+ private final SimpMessagingTemplate template;
+
+ private volatile boolean isBrokerAvailable = true;
+
+ @Autowired
+ public RestGreetingServiceImpl(SimpMessagingTemplate template) {
+ this.template = template;
+ }
+
+ @Override
+ public void doGreetings(String greeting) {
+ String text = "[" + LocalDateTime.now() + "]:" + greeting;
+
+ if (isBrokerAvailable) {
+ // STOMP clients subscribed to /topic/greeting will receive the data sent by the convertAndSend method.
+ template.convertAndSend("/topic/greeting", text);
+ }
+ }
+
+ @Override
+ public void onApplicationEvent(BrokerAvailabilityEvent event) {
+ // Components using the SimpMessagingTemplate should subscribe to this event
+ // and avoid sending messages at times when the broker is not available.
+ // In any case they should be prepared to handle MessageDeliveryException
+ // when sending a message.
+ isBrokerAvailable = event.isBrokerAvailable();
+ }
+}
--- /dev/null
+package de.spring.stomp.services.impl;
+
+import java.time.LocalDateTime;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
+import org.springframework.stereotype.Service;
+
+import de.spring.stomp.services.UserTradeService;
+
+@Service("userTradeService")
+public class UserTradeServiceImpl
+ implements UserTradeService, ApplicationListener<BrokerAvailabilityEvent> {
+ private final SimpMessagingTemplate template;
+
+ private volatile boolean isBrokerAvailable = true;
+
+ @Autowired
+ public UserTradeServiceImpl(SimpMessagingTemplate template) {
+ this.template = template;
+ }
+
+ @Override
+ public void doTrade(String user) {
+ String text = "[" + LocalDateTime.now() + "]:" + user;
+
+ if (isBrokerAvailable) {
+ // STOMP clients subscribed to /topic/position-updates will receive the data sent by the convertAndSend method.
+ template.convertAndSendToUser(user, "/topic/position-updates", text);
+ }
+ }
+
+ @Override
+ public void onApplicationEvent(BrokerAvailabilityEvent event) {
+ // Components using the SimpMessagingTemplate should subscribe to this event
+ // and avoid sending messages at times when the broker is not available.
+ // In any case they should be prepared to handle MessageDeliveryException
+ // when sending a message.
+ isBrokerAvailable = event.isBrokerAvailable();
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.spring.stomp.services.RestGreetingService;
+
+
+@RestController
+public class RestGreetingController {
+ private final RestGreetingService restGreetingService;
+
+ @Autowired
+ public RestGreetingController(RestGreetingService restGreetingService) {
+ this.restGreetingService = restGreetingService;
+ }
+
+ // Sending data to /topic/greeting from REST service.
+ // POST http://localhost:8080/spring-stomp-server/greetings
+
+ @RequestMapping(path="/greetings", method=RequestMethod.POST)
+ public void handle(@RequestBody String greeting) {
+
+ // STOMP clients subscribed to /topic/greeting will receive the data sent by the convertAndSend method.
+ restGreetingService.doGreetings(greeting);
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.controller;
+
+import java.time.LocalDateTime;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import de.spring.stomp.services.UserTradeService;
+
+public class UserTradeController {
+ private final UserTradeService userTradeService;
+
+ @Autowired
+ public UserTradeController(UserTradeService userTradeService) {
+ this.userTradeService = userTradeService;
+ }
+
+ // Sending data to /topic/greeting from REST service.
+ // POST http://localhost:8080/spring-stomp-server/trade
+
+ @RequestMapping(path="/trade", method=RequestMethod.POST)
+ public void handle(@RequestBody String user) {
+ userTradeService.doTrade(user);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ status: The level of internal Log4j events that should be logged to the console.
+ Valid values for this attribute are "trace", "debug", "info", "warn", "error" and "fatal".
+
+ monitorInterval: The minimum amount of time, in seconds, that must elapse before the file configuration is checked for changes.
+
+
+ see https://logging.apache.org/log4j/2.x/manual/configuration.html
+ -->
+<Configuration status="error" strict="true" monitorInterval="30"
+ name="XMLConfigTest" packages="org.apache.logging.log4j.test">
+
+ <!--
+ ALL > TRACE > DEBUG > INFO > WARN > ERROR > OFF
+
+ ERROR by default.
+ -->
+
+ <Appenders>
+ <Appender type="Console" name="STDOUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
+ </Appender>
+ </Appenders>
+ <Loggers>
+
+ <!--
+ SockJS client disconnects. Requires TRACE level always.
+ It works because I know for sure I will be using WebSockets when using SockJS.
+ -->
+ <Logger
+ name="org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession"
+ level="TRACE" additivity="false">
+ <AppenderRef ref="STDOUT" />
+ </Logger>
+
+ <!--
+ General logging Spring.
+ -->
+ <Logger name="org.springframework" level="INFO" additivity="false">
+ <AppenderRef ref="STDOUT" />
+ </Logger>
+
+
+ <!--
+ Anything else will be using TRACE logging level.
+ -->
+ <Root level="INFO">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+</Configuration>
--- /dev/null
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/mvc
+ http://www.springframework.org/schema/mvc/spring-mvc.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <!--
+ I am declaring my beans without the automatic annotation. :/
+ Better because we are saving memory but it requires more configuration.
+
+ See: org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
+ <mvc:annotation-driven/>
+ -->
+
+
+ <context:annotation-config />
+
+ <context:component-scan base-package="de.spring.webservices.rest"/>
+
+ <!--
+ Required beans for generating XML responses from Java objects using JAXB annotations
+ Jackson also works but it doesn't generate XML with namespaces... O.o
+
+ This implementation will be slower than the one using Jackson :( but I am going to use it just for WADL generation :)
+ -->
+ <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
+ <property name="packagesToScan" value="org.jvnet.ws.wadl"/>
+ </bean>
+ <bean id="jaxbConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
+ <constructor-arg ref="jaxbMarshaller" />
+ </bean>
+
+ <!-- Required beans for generating JSON responses from Java objects -->
+ <bean id="jsonObjectMapperFactory" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
+ p:indentOutput="true" p:failOnEmptyBeans="false">
+ <property name="featuresToDisable">
+ <array>
+ <util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES"/>
+ <util:constant static-field="com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION"/>
+ </array>
+ </property>
+ </bean>
+
+ <util:list id="messageConverters">
+ <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" p:objectMapper-ref="jsonObjectMapperFactory"/>
+ <ref bean="jaxbConverter" />
+ <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
+ </util:list>
+
+
+ <bean name="handlerAdapter"
+ class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
+ <property name="webBindingInitializer">
+ <bean
+ class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
+ <!-- It enables us to use JSR-303 -->
+ <property name="validator">
+ <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
+ </property>
+ </bean>
+ </property>
+ <property name="messageConverters" ref="messageConverters" />
+
+
+ <property name="requestBodyAdvice">
+ <util:list>
+ <bean id="requestBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice"/>
+ </util:list>
+ </property>
+
+
+ <property name="responseBodyAdvice">
+ <util:list>
+ <bean id="responseBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice"/>
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
+
+ <mvc:default-servlet-handler />
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:websocket="http://www.springframework.org/schema/websocket"
+
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/websocket
+ http://www.springframework.org/schema/websocket/spring-websocket.xsd">
+
+ <!--
+ Searches for beans in packages (instead of XML configuration we can use
+ in this way annotations like @Service, @Endpoint, etc, etc)
+ -->
+ <context:component-scan base-package="de.spring.stomp"/>
+
+
+ <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
+ <property name="maxTextMessageBufferSize" value="8192"/>
+ <property name="maxBinaryMessageBufferSize" value="8192"/>
+ </bean>
+
+ <!--
+ Note that even though the STOMP CONNECT frame has "login" and "passcode" headers that can be used for
+ authentication, Spring’s STOMP WebSocket support ignores them and currently expects users to have been
+ authenticated already via HTTP.
+
+ In some cases it may be useful to assign an identity to a WebSocket session even when the user has not
+ been formally authenticated. For example, a mobile app might assign some identity to anonymous users,
+ perhaps based on geographical location. The do that currently, an application can sub-class DefaultHandshakeHandler
+ and override the determineUser method. The custom handshake handler can then be plugged in
+ (see examples in Section 25.2.4, “Deployment Considerations”).
+ -->
+ <bean id="customHandshakeHandler" class="de.spring.stomp.handlers.CustomHandshakeHandler"/>
+
+ <!-- Interceptors -->
+ <bean id="customChannelInterceptor" class="de.spring.stomp.interceptors.CustomChannelInterceptor"/>
+ <bean id="customHttpHandshakeInterceptor" class="de.spring.stomp.interceptors.CustomHttpHandshakeInterceptor"/>
+
+ <!-- Listeners -->
+ <bean id="brokerAvailabilityListener" class="de.spring.stomp.listeners.BrokerAvailabilityListener"/>
+ <bean id="sessionConnectedListener" class="de.spring.stomp.listeners.SessionConnectedListener"/>
+ <bean id="sessionConnectListener" class="de.spring.stomp.listeners.SessionConnectListener"/>
+ <bean id="sessionDisconnectListener" class="de.spring.stomp.listeners.SessionDisconnectListener"/>
+ <bean id="sessionSubscribeListener" class="de.spring.stomp.listeners.SessionSubscribeListener"/>
+ <bean id="sessionUnsubscribeListener" class="de.spring.stomp.listeners.SessionUnsubscribeListener"/>
+
+ <!-- Scheduler -->
+ <bean id="simpleBrokerScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" />
+
+ <!-- STOMP -->
+ <!-- Simple broker -->
+ <websocket:message-broker application-destination-prefix="/app">
+ <websocket:transport send-timeout="15000" send-buffer-size="524288" message-size="131072" />
+ <websocket:stomp-endpoint path="/portfolio" allowed-origins="*">
+ <websocket:handshake-handler ref="customHandshakeHandler" />
+ <websocket:handshake-interceptors>
+ <ref bean="customHttpHandshakeInterceptor"/>
+ </websocket:handshake-interceptors>
+ <websocket:sockjs/>
+ </websocket:stomp-endpoint>
+ <!--
+ In memory broker.
+
+ hearbeat: Configure the value for the heartbeat settings. The first number represents how often the server will
+ write or send a heartbeat. The second is how often the client should write. 0 means no heartbeats.
+ By default this is set to "0, 0" unless the scheduler attribute is also set in which case the
+ default becomes "10000,10000" (in milliseconds).
+
+ Matching the value used by client.
+ -->
+ <websocket:simple-broker prefix="/topic, /queue"
+ scheduler="simpleBrokerScheduler" heartbeat="0,20000" />
+
+ <websocket:client-inbound-channel>
+ <websocket:executor core-pool-size="100" max-pool-size="200" keep-alive-seconds="600"/>
+ <websocket:interceptors>
+ <ref bean="customChannelInterceptor"/>
+ </websocket:interceptors>
+ </websocket:client-inbound-channel>
+ <websocket:client-outbound-channel>
+ <websocket:executor core-pool-size="101" max-pool-size="201" keep-alive-seconds="601"/>
+ <websocket:interceptors>
+ <ref bean="customChannelInterceptor"/>
+ </websocket:interceptors>
+ </websocket:client-outbound-channel>
+ </websocket:message-broker>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="2.4">
+
+ <display-name>Spring STOMP: example simple broker</display-name>
+
+ <listener>
+ <listener-class>
+ org.springframework.web.context.ContextLoaderListener
+ </listener-class>
+ </listener>
+
+ <context-param>
+ <param-name>spring.profiles.default</param-name>
+ <param-value>${environment.profile}</param-value>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>
+ classpath*:spring-configuration/*.xml
+ </param-value>
+ </context-param>
+
+ <!-- Spring REST support -->
+ <servlet>
+ <servlet-name>spring-rest</servlet-name>
+ <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ <init-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>classpath*:spring-configuration/mvc/rest/*.xml</param-value>
+ </init-param>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>spring-rest</servlet-name>
+ <!-- REQUIRED PATTERN BY swagger-ui. IT DOESN'T WORK WITH ANY OTHER o.O -->
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
+++ /dev/null
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>de.spring.stomp</groupId>
- <artifactId>spring-stomp</artifactId>
- <version>1.0-SNAPSHOT</version>
- </parent>
-
- <artifactId>spring-stomp-server</artifactId>
- <packaging>war</packaging>
- <name>spring-stomp-server</name>
- <url>http://gumartinm.name</url>
- <description>STOMP with Spring Framework.</description>
- <organization>
- <name>Gustavo Martin Morcuende</name>
- <url>http://www.gumartinm.name</url>
- </organization>
- <scm>
- <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun</developerConnection>
- <url>http://git.gumartinm.name/JavaForFun</url>
- </scm>
-
-
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-oxm</artifactId>
- </dependency>
-
- <!-- Required for WebSockets -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-websocket</artifactId>
- </dependency>
-
- <!-- Required for STOMP -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-messaging</artifactId>
- </dependency>
-
- <!-- Required by spring-webmvc -->
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <scope>provided</scope>
- </dependency>
-
- <!--
- Jackson JSON Processor, required by spring-webmvc. See messageConverters
- in rest-config.xml
- -->
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- </dependency>
-
-
- <!--
- Required by spring-context for using JSR-303. See LocalValidatorFactoryBean
- in rest-config.xml
- -->
- <dependency>
- <groupId>javax.validation</groupId>
- <artifactId>validation-api</artifactId>
- </dependency>
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-validator</artifactId>
- </dependency>
-
-
- <!-- Full-featured broker, TCP connection management. -->
- <dependency>
- <groupId>org.projectreactor</groupId>
- <artifactId>reactor-net</artifactId>
- <version>1.1.6.RELEASE</version>
- </dependency>
-
- </dependencies>
- <build>
- <finalName>${project.artifactId}</finalName>
- <resources>
- <resource>
- <directory>${basedir}/src/main/webapp</directory>
- <excludes>
- <exclude>**/*.*</exclude>
- </excludes>
- </resource>
- <resource>
- <directory>${basedir}/src/main/resources/</directory>
- <includes>
- <include>**/*.*</include>
- </includes>
- </resource>
- </resources>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-war-plugin</artifactId>
- <version>2.6</version>
- <configuration>
- <webResources>
- <resource>
- <filtering>true</filtering>
- <directory>src/main/webapp</directory>
- <includes>
- <include>WEB-INF/web.xml</include>
- </includes>
- </resource>
- </webResources>
- </configuration>
- </plugin>
- </plugins>
- </build>
-</project>
+++ /dev/null
-package de.spring.stomp.controllers;
-
-import java.time.LocalDateTime;
-
-import org.springframework.messaging.handler.annotation.MessageMapping;
-import org.springframework.messaging.handler.annotation.SendTo;
-import org.springframework.stereotype.Controller;
-
-@Controller
-public class MessageGreetingController {
-
- // Sending data to /app/greeting from STOMP client (client must first connect to endpoint, in my case portfolio)
- // connecting to this URL -> http://172.17.0.3/spring-stomp-server/portfolio
- // sending data to /app/greeting
-
- // The data sent to /app/greeting will be retrieved by this method.
- @MessageMapping("/greeting")
- @SendTo("/topic/greeting")
- public String handle(String greeting) {
- // STOMP clients subscribed to /topic/greeting will receive the returned data from this method.
- // Destination is selected based on a convention but can be overridden via @SendTo
- // I will be using @SendTo. In my case, it is not required (because it is the same as the destination selected
- // based on the convention) but I will be using it just for fun.
- return "[" + LocalDateTime.now() + "]: " + greeting;
- }
-}
+++ /dev/null
-package de.spring.stomp.controllers;
-
-import java.security.Principal;
-import java.time.LocalDateTime;
-
-import org.springframework.messaging.handler.annotation.MessageMapping;
-import org.springframework.messaging.simp.annotation.SendToUser;
-import org.springframework.stereotype.Controller;
-
-@Controller
-public class UserMessageTradeController {
-
- @MessageMapping("/trade")
- @SendToUser(destinations="/topic/position-updates", broadcast=false /* No idea what is this for */)
- public String executeTrade(String trade, Principal principal) {
-
- return "[" + LocalDateTime.now() + "]: " + trade;
- }
-}
+++ /dev/null
-package de.spring.stomp.handlers;
-
-import java.security.Principal;
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.web.socket.WebSocketHandler;
-import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
-
-/**
- * In some cases it may be useful to assign an identity to a WebSocket session even when
- * the user has not been formally authenticated. For example, a mobile app might assign some
- * identity to anonymous users, perhaps based on geographical location. The do that currently,
- * an application can sub-class DefaultHandshakeHandler and override the determineUser method.
- * The custom handshake handler can then be plugged in (see examples in
- * Section 25.2.4, “Deployment Considerations”)
- */
-public class CustomHandshakeHandler extends DefaultHandshakeHandler {
- private static final Logger LOGGER = LoggerFactory.getLogger(CustomHandshakeHandler.class);
-
- @Override
- protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
- Map<String, Object> attributes) {
-
- Principal principal = request.getPrincipal();
- LOGGER.info("CustomHandshakeHandler: " + principal.getName());
-
- return super.determineUser(request, wsHandler, attributes);
- }
-
-}
+++ /dev/null
-package de.spring.stomp.interceptors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageChannel;
-import org.springframework.messaging.simp.stomp.StompCommand;
-import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
-import org.springframework.messaging.support.ChannelInterceptorAdapter;
-
-public class CustomChannelInterceptor extends ChannelInterceptorAdapter {
- private static final Logger LOGGER = LoggerFactory.getLogger(CustomChannelInterceptor.class);
-
- @Override
- public Message<?> preSend(Message<?> message, MessageChannel channel) {
- StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
- StompCommand command = accessor.getCommand();
-
- LOGGER.info("CustomChannelInterceptor preSend, StompCommand: " + command);
- LOGGER.info("CustomChannelInterceptor preSend, login: " + accessor.getLogin());
- LOGGER.info("CustomChannelInterceptor preSend, heartBeat: " + accessor.getHeartbeat());
- LOGGER.info("CustomChannelInterceptor preSend, destination: " + accessor.getDestination());
- LOGGER.info("CustomChannelInterceptor preSend, host: " + accessor.getHost());
-
- return message;
- }
-
- @Override
- public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
- StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
- StompCommand command = accessor.getCommand();
-
- LOGGER.info("CustomChannelInterceptor postSend, StompCommand: " + command);
- LOGGER.info("CustomChannelInterceptor postSend, login: " + accessor.getLogin());
- LOGGER.info("CustomChannelInterceptor postSend, heartBeat: " + accessor.getHeartbeat());
- LOGGER.info("CustomChannelInterceptor postSend, destination: " + accessor.getDestination());
- LOGGER.info("CustomChannelInterceptor postSend, host: " + accessor.getHost());
-
- }
-}
+++ /dev/null
-package de.spring.stomp.interceptors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.http.server.ServerHttpResponse;
-import org.springframework.web.socket.WebSocketHandler;
-import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
-
-public class CustomHttpHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
- private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpHandshakeInterceptor.class);
-
-
- @Override
- public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
- WebSocketHandler wsHandler, Exception ex) {
- super.afterHandshake(request, response, wsHandler, ex);
-
- LOGGER.info("Request URI:" + request.getURI());
- LOGGER.info("Request remote address:" + request.getRemoteAddress());
- LOGGER.info("Request local address:" + request.getLocalAddress());
- LOGGER.info("Request headers:" + request.getHeaders());
-
- LOGGER.info("Response headers:" + response.getHeaders());
- }
-}
+++ /dev/null
-package de.spring.stomp.listeners;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationListener;
-import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
-
-public class BrokerAvailabilityListener implements ApplicationListener<BrokerAvailabilityEvent> {
- private static final Logger LOGGER = LoggerFactory.getLogger(BrokerAvailabilityListener.class);
-
- @Override
- public void onApplicationEvent(BrokerAvailabilityEvent event) {
-
- LOGGER.info("BrokerAvailabilityEvent timestamp: " + event.getTimestamp());
- LOGGER.info("BrokerAvailabilityEvent brokerAvailable: " + event.isBrokerAvailable());
- LOGGER.info("BrokerAvailabilityEvent: " + event.toString());
- }
-
-}
+++ /dev/null
-package de.spring.stomp.listeners;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationListener;
-import org.springframework.web.socket.messaging.SessionConnectEvent;
-
-public class SessionConnectListener implements ApplicationListener<SessionConnectEvent> {
- private static final Logger LOGGER = LoggerFactory.getLogger(SessionConnectListener.class);
-
- @Override
- public void onApplicationEvent(SessionConnectEvent event) {
- LOGGER.info("SessionConnectEvent timestamp: " + event.getTimestamp());
- LOGGER.info("SessionConnectEvent user: " + event.getUser());
- LOGGER.info("SessionConnectEvent: " + event.toString());
- }
-
-}
+++ /dev/null
-package de.spring.stomp.listeners;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationListener;
-import org.springframework.web.socket.messaging.SessionConnectedEvent;
-
-public class SessionConnectedListener implements ApplicationListener<SessionConnectedEvent> {
- private static final Logger LOGGER = LoggerFactory.getLogger(SessionConnectedListener.class);
-
- @Override
- public void onApplicationEvent(SessionConnectedEvent event) {
- LOGGER.info("SessionConnectedEvent timestamp: " + event.getTimestamp());
- LOGGER.info("SessionConnectedEvent user: " + event.getUser());
- LOGGER.info("SessionConnectedEvent: " + event.toString());
- }
-
-}
+++ /dev/null
-package de.spring.stomp.listeners;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationListener;
-import org.springframework.web.socket.messaging.SessionDisconnectEvent;
-
-public class SessionDisconnectListener implements ApplicationListener<SessionDisconnectEvent> {
- private static final Logger LOGGER = LoggerFactory.getLogger(SessionUnsubscribeListener.class);
-
- @Override
- public void onApplicationEvent(SessionDisconnectEvent event) {
- LOGGER.info("SessionDisconnectEvent timestamp: " + event.getTimestamp());
- LOGGER.info("SessionDisconnectEvent user: " + event.getUser());
- LOGGER.info("SessionDisconnectEvent sessionId: " + event.getSessionId());
- LOGGER.info("SessionDisconnectEvent close status: " + event.getCloseStatus());
- LOGGER.info("SessionDisconnectEvent: " + event.toString());
- }
-
-}
+++ /dev/null
-package de.spring.stomp.listeners;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationListener;
-import org.springframework.web.socket.messaging.SessionSubscribeEvent;
-
-public class SessionSubscribeListener implements ApplicationListener<SessionSubscribeEvent> {
- private static final Logger LOGGER = LoggerFactory.getLogger(SessionSubscribeListener.class);
-
- @Override
- public void onApplicationEvent(SessionSubscribeEvent event) {
- LOGGER.info("SessionSubscribeEvent timestamp: " + event.getTimestamp());
- LOGGER.info("SessionSubscribeEvent user: " + event.getUser());
- LOGGER.info("SessionSubscribeEvent: " + event.toString());
- }
-
-}
+++ /dev/null
-package de.spring.stomp.listeners;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationListener;
-import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;
-
-public class SessionUnsubscribeListener implements ApplicationListener<SessionUnsubscribeEvent> {
- private static final Logger LOGGER = LoggerFactory.getLogger(SessionUnsubscribeListener.class);
-
- @Override
- public void onApplicationEvent(SessionUnsubscribeEvent event) {
- LOGGER.info("SessionUnsubscribeEvent timestamp: " + event.getTimestamp());
- LOGGER.info("SessionUnsubscribeEvent user: " + event.getUser());
- LOGGER.info("SessionUnsubscribeEvent: " + event.toString());
- }
-
-}
+++ /dev/null
-package de.spring.stomp.services;
-
-public interface RestGreetingService {
-
- void doGreetings(String greeting);
-}
+++ /dev/null
-package de.spring.stomp.services;
-
-public interface UserTradeService {
-
- void doTrade(String user);
-}
+++ /dev/null
-package de.spring.stomp.services.impl;
-
-import java.time.LocalDateTime;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationListener;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
-import org.springframework.stereotype.Service;
-
-import de.spring.stomp.services.RestGreetingService;
-
-@Service("restGreetingService")
-public class RestGreetingServiceImpl
- implements RestGreetingService, ApplicationListener<BrokerAvailabilityEvent> {
- private final SimpMessagingTemplate template;
-
- private volatile boolean isBrokerAvailable = true;
-
- @Autowired
- public RestGreetingServiceImpl(SimpMessagingTemplate template) {
- this.template = template;
- }
-
- @Override
- public void doGreetings(String greeting) {
- String text = "[" + LocalDateTime.now() + "]:" + greeting;
-
- if (isBrokerAvailable) {
- // STOMP clients subscribed to /topic/greeting will receive the data sent by the convertAndSend method.
- template.convertAndSend("/topic/greeting", text);
- }
- }
-
- @Override
- public void onApplicationEvent(BrokerAvailabilityEvent event) {
- // Components using the SimpMessagingTemplate should subscribe to this event
- // and avoid sending messages at times when the broker is not available.
- // In any case they should be prepared to handle MessageDeliveryException
- // when sending a message.
- isBrokerAvailable = event.isBrokerAvailable();
- }
-}
+++ /dev/null
-package de.spring.stomp.services.impl;
-
-import java.time.LocalDateTime;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationListener;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
-import org.springframework.stereotype.Service;
-
-import de.spring.stomp.services.UserTradeService;
-
-@Service("userTradeService")
-public class UserTradeServiceImpl
- implements UserTradeService, ApplicationListener<BrokerAvailabilityEvent> {
- private final SimpMessagingTemplate template;
-
- private volatile boolean isBrokerAvailable = true;
-
- @Autowired
- public UserTradeServiceImpl(SimpMessagingTemplate template) {
- this.template = template;
- }
-
- @Override
- public void doTrade(String user) {
- String text = "[" + LocalDateTime.now() + "]:" + user;
-
- if (isBrokerAvailable) {
- // STOMP clients subscribed to /topic/position-updates will receive the data sent by the convertAndSend method.
- template.convertAndSendToUser(user, "/topic/position-updates", text);
- }
- }
-
- @Override
- public void onApplicationEvent(BrokerAvailabilityEvent event) {
- // Components using the SimpMessagingTemplate should subscribe to this event
- // and avoid sending messages at times when the broker is not available.
- // In any case they should be prepared to handle MessageDeliveryException
- // when sending a message.
- isBrokerAvailable = event.isBrokerAvailable();
- }
-
-}
+++ /dev/null
-package de.spring.webservices.rest.controller;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-import de.spring.stomp.services.RestGreetingService;
-
-
-@RestController
-public class RestGreetingController {
- private final RestGreetingService restGreetingService;
-
- @Autowired
- public RestGreetingController(RestGreetingService restGreetingService) {
- this.restGreetingService = restGreetingService;
- }
-
- // Sending data to /topic/greeting from REST service.
- // POST http://localhost:8080/spring-stomp-server/greetings
-
- @RequestMapping(path="/greetings", method=RequestMethod.POST)
- public void handle(@RequestBody String greeting) {
-
- // STOMP clients subscribed to /topic/greeting will receive the data sent by the convertAndSend method.
- restGreetingService.doGreetings(greeting);
- }
-
-}
+++ /dev/null
-package de.spring.webservices.rest.controller;
-
-import java.time.LocalDateTime;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-
-import de.spring.stomp.services.UserTradeService;
-
-public class UserTradeController {
- private final UserTradeService userTradeService;
-
- @Autowired
- public UserTradeController(UserTradeService userTradeService) {
- this.userTradeService = userTradeService;
- }
-
- // Sending data to /topic/greeting from REST service.
- // POST http://localhost:8080/spring-stomp-server/trade
-
- @RequestMapping(path="/trade", method=RequestMethod.POST)
- public void handle(@RequestBody String user) {
- userTradeService.doTrade(user);
- }
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
- status: The level of internal Log4j events that should be logged to the console.
- Valid values for this attribute are "trace", "debug", "info", "warn", "error" and "fatal".
-
- monitorInterval: The minimum amount of time, in seconds, that must elapse before the file configuration is checked for changes.
-
-
- see https://logging.apache.org/log4j/2.x/manual/configuration.html
- -->
-<Configuration status="error" strict="true" monitorInterval="30"
- name="XMLConfigTest" packages="org.apache.logging.log4j.test">
-
- <!--
- ALL > TRACE > DEBUG > INFO > WARN > ERROR > OFF
-
- ERROR by default.
- -->
-
- <Appenders>
- <Appender type="Console" name="STDOUT">
- <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
- </Appender>
- </Appenders>
- <Loggers>
-
- <!--
- SockJS client disconnects. Requires TRACE level always.
- It works because I know for sure I will be using WebSockets when using SockJS.
- -->
- <Logger
- name="org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession"
- level="TRACE" additivity="false">
- <AppenderRef ref="STDOUT" />
- </Logger>
-
- <!--
- General logging Spring.
- -->
- <Logger name="org.springframework" level="INFO" additivity="false">
- <AppenderRef ref="STDOUT" />
- </Logger>
-
-
- <!--
- Anything else will be using TRACE logging level.
- -->
- <Root level="INFO">
- <AppenderRef ref="STDOUT"/>
- </Root>
- </Loggers>
-</Configuration>
+++ /dev/null
-<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:util="http://www.springframework.org/schema/util"
- xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/mvc
- http://www.springframework.org/schema/mvc/spring-mvc.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/util
- http://www.springframework.org/schema/util/spring-util.xsd">
-
- <!--
- I am declaring my beans without the automatic annotation. :/
- Better because we are saving memory but it requires more configuration.
-
- See: org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
- <mvc:annotation-driven/>
- -->
-
-
- <context:annotation-config />
-
- <context:component-scan base-package="de.spring.webservices.rest"/>
-
- <!--
- Required beans for generating XML responses from Java objects using JAXB annotations
- Jackson also works but it doesn't generate XML with namespaces... O.o
-
- This implementation will be slower than the one using Jackson :( but I am going to use it just for WADL generation :)
- -->
- <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
- <property name="packagesToScan" value="org.jvnet.ws.wadl"/>
- </bean>
- <bean id="jaxbConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
- <constructor-arg ref="jaxbMarshaller" />
- </bean>
-
- <!-- Required beans for generating JSON responses from Java objects -->
- <bean id="jsonObjectMapperFactory" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
- p:indentOutput="true" p:failOnEmptyBeans="false">
- <property name="featuresToDisable">
- <array>
- <util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES"/>
- <util:constant static-field="com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION"/>
- </array>
- </property>
- </bean>
-
- <util:list id="messageConverters">
- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" p:objectMapper-ref="jsonObjectMapperFactory"/>
- <ref bean="jaxbConverter" />
- <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
- </util:list>
-
-
- <bean name="handlerAdapter"
- class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
- <property name="webBindingInitializer">
- <bean
- class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
- <!-- It enables us to use JSR-303 -->
- <property name="validator">
- <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
- </property>
- </bean>
- </property>
- <property name="messageConverters" ref="messageConverters" />
-
-
- <property name="requestBodyAdvice">
- <util:list>
- <bean id="requestBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice"/>
- </util:list>
- </property>
-
-
- <property name="responseBodyAdvice">
- <util:list>
- <bean id="responseBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice"/>
- </util:list>
- </property>
- </bean>
-
- <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
-
- <mvc:default-servlet-handler />
-
-</beans>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:websocket="http://www.springframework.org/schema/websocket"
-
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/websocket
- http://www.springframework.org/schema/websocket/spring-websocket.xsd">
-
- <!--
- Searches for beans in packages (instead of XML configuration we can use
- in this way annotations like @Service, @Endpoint, etc, etc)
- -->
- <context:component-scan base-package="de.spring.stomp"/>
-
-
- <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
- <property name="maxTextMessageBufferSize" value="8192"/>
- <property name="maxBinaryMessageBufferSize" value="8192"/>
- </bean>
-
- <!--
- Note that even though the STOMP CONNECT frame has "login" and "passcode" headers that can be used for
- authentication, Spring’s STOMP WebSocket support ignores them and currently expects users to have been
- authenticated already via HTTP.
-
- In some cases it may be useful to assign an identity to a WebSocket session even when the user has not
- been formally authenticated. For example, a mobile app might assign some identity to anonymous users,
- perhaps based on geographical location. The do that currently, an application can sub-class DefaultHandshakeHandler
- and override the determineUser method. The custom handshake handler can then be plugged in
- (see examples in Section 25.2.4, “Deployment Considerations”).
- -->
- <bean id="customHandshakeHandler" class="de.spring.stomp.handlers.CustomHandshakeHandler"/>
-
- <!-- Interceptors -->
- <bean id="customChannelInterceptor" class="de.spring.stomp.interceptors.CustomChannelInterceptor"/>
- <bean id="customHttpHandshakeInterceptor" class="de.spring.stomp.interceptors.CustomHttpHandshakeInterceptor"/>
-
- <!-- Listeners -->
- <bean id="brokerAvailabilityListener" class="de.spring.stomp.listeners.BrokerAvailabilityListener"/>
- <bean id="sessionConnectedListener" class="de.spring.stomp.listeners.SessionConnectedListener"/>
- <bean id="sessionConnectListener" class="de.spring.stomp.listeners.SessionConnectListener"/>
- <bean id="sessionDisconnectListener" class="de.spring.stomp.listeners.SessionDisconnectListener"/>
- <bean id="sessionSubscribeListener" class="de.spring.stomp.listeners.SessionSubscribeListener"/>
- <bean id="sessionUnsubscribeListener" class="de.spring.stomp.listeners.SessionUnsubscribeListener"/>
-
- <!-- STOMP -->
- <!-- Simple broker -->
- <websocket:message-broker application-destination-prefix="/app">
- <websocket:transport send-timeout="15000" send-buffer-size="524288" message-size="131072" />
- <websocket:stomp-endpoint path="/portfolio" allowed-origins="*">
- <websocket:handshake-handler ref="customHandshakeHandler" />
- <websocket:handshake-interceptors>
- <ref bean="customHttpHandshakeInterceptor"/>
- </websocket:handshake-interceptors>
- <websocket:sockjs/>
- </websocket:stomp-endpoint>
- <!--
- In memory broker.
-
- hearbeat: Configure the value for the heartbeat settings. The first number represents how often the server will
- write or send a heartbeat. The second is how often the client should write. 0 means no heartbeats.
- By default this is set to "0, 0" unless the scheduler attribute is also set in which case the
- default becomes "10000,10000" (in milliseconds).
-
- Matching the value used by client.
- -->
- <websocket:simple-broker prefix="/topic, /queue"
- heartbeat="0,20000" />
-
- <websocket:client-inbound-channel>
- <websocket:executor core-pool-size="100" max-pool-size="200" keep-alive-seconds="600"/>
- <websocket:interceptors>
- <ref bean="customChannelInterceptor"/>
- </websocket:interceptors>
- </websocket:client-inbound-channel>
- <websocket:client-outbound-channel>
- <websocket:executor core-pool-size="101" max-pool-size="201" keep-alive-seconds="601"/>
- <websocket:interceptors>
- <ref bean="customChannelInterceptor"/>
- </websocket:interceptors>
- </websocket:client-outbound-channel>
- </websocket:message-broker>
-
- <!-- Full-featured broker -->
- <websocket:message-broker application-destination-prefix="/app">
- <websocket:transport send-timeout="15000" send-buffer-size="524288" message-size="131072" />
- <websocket:stomp-endpoint path="/fullportfolio" allowed-origins="*">
- <websocket:handshake-handler ref="customHandshakeHandler" />
- <websocket:handshake-interceptors>
- <ref bean="customHttpHandshakeInterceptor"/>
- </websocket:handshake-interceptors>
- <websocket:sockjs/>
- </websocket:stomp-endpoint>
- <!--
- Full-featured broker
- -->
- <websocket:stomp-broker-relay prefix="/topic, /queue"
- relay-host="" relay-port="" client-login="" client-passcode="" system-login="" system-passcode=""
- heartbeat-send-interval="" heartbeat-receive-interval="" auto-startup="true" virtual-host=""/>
-
- <websocket:client-inbound-channel>
- <websocket:executor core-pool-size="100" max-pool-size="200" keep-alive-seconds="600"/>
- <websocket:interceptors>
- <ref bean="customChannelInterceptor"/>
- </websocket:interceptors>
- </websocket:client-inbound-channel>
- <websocket:client-outbound-channel>
- <websocket:executor core-pool-size="101" max-pool-size="201" keep-alive-seconds="601"/>
- <websocket:interceptors>
- <ref bean="customChannelInterceptor"/>
- </websocket:interceptors>
- </websocket:client-outbound-channel>
- </websocket:message-broker>
-
-</beans>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
- version="2.4">
-
- <display-name>Spring STOMP: example</display-name>
-
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
-
- <context-param>
- <param-name>spring.profiles.active</param-name>
- <param-value>${environment.profile}</param-value>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath*:spring-configuration/*.xml
- </param-value>
- </context-param>
-
- <!-- Spring REST support -->
- <servlet>
- <servlet-name>spring-rest</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <load-on-startup>1</load-on-startup>
- <async-supported>true</async-supported>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath*:spring-configuration/mvc/rest/*.xml</param-value>
- </init-param>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>spring-rest</servlet-name>
- <!-- REQUIRED PATTERN BY swagger-ui. IT DOESN'T WORK WITH ANY OTHER o.O -->
- <url-pattern>/*</url-pattern>
- </servlet-mapping>
-
-</web-app>
+++ /dev/null
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>de.spring.stomp</groupId>
- <artifactId>spring-stomp</artifactId>
- <packaging>pom</packaging>
- <version>1.0-SNAPSHOT</version>
- <name>spring-stomp</name>
- <url>http://gumartinm.name</url>
- <description>Emails with Spring Framework</description>
- <organization>
- <name>Gustavo Martin Morcuende</name>
- <url>http://www.gumartinm.name</url>
- </organization>
- <scm>
- <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun</developerConnection>
- <url>http://git.gumartinm.name/JavaForFun</url>
- </scm>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <spring.version>4.2.5.RELEASE</spring.version>
- </properties>
-
- <profiles>
- <profile>
- <id>release</id>
- <properties>
- <environment.profile>release</environment.profile>
- </properties>
- <activation>
- <activeByDefault>true</activeByDefault>
- </activation>
- </profile>
- </profiles>
-
- <dependencies>
- <!--
- 1/3 Required dependency for log4j 2 with slf4j: binding between log4j
- 2 and slf4j
- -->
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-slf4j-impl</artifactId>
- <version>2.3</version>
- </dependency>
- <!--
- 2/3 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin
- (it is the log4j 2 implementation)
- -->
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <version>2.3</version>
- </dependency>
- <!--
- 3/3 Required dependency for getting rid of commons logging. This is
- the BRIDGE (no binding) between Jakarta Commons Logging (used by Spring)
- and whatever I am using for logging (in this case I am using log4j 2)
- See: http://www.slf4j.org/legacy.html We need exclusions in every dependency using
- Jakarta Commons Logging (see Spring dependencies below)
- -->
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jcl-over-slf4j</artifactId>
- <version>1.7.12</version>
- </dependency>
-
-
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>2.2.2</version>
- </dependency>
-
- <!-- Unitary and integration tests -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <version>2.0.43-beta</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- <!--
- Required dependency for getting rid of commons logging and use my
- own logging library (in my case I decided to use log4j 2 under slf4j)
- -->
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- <!-- Required for WebSockets -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-websocket</artifactId>
- <version>${spring.version}</version>
- <!--
- Required dependency for getting rid of commons logging and use my
- own logging library (in my case I decided to use log4j 2 under slf4j)
- -->
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- <!-- Required for STOMP -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-messaging</artifactId>
- <version>${spring.version}</version>
- <!--
- Required dependency for getting rid of commons logging and use my
- own logging library (in my case I decided to use log4j 2 under slf4j)
- -->
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- <!-- REST API -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</artifactId>
- <version>${spring.version}</version>
- <!--
- Required dependency for getting rid of commons logging and use my
- own logging library (in my case I decided to use log4j 2 under slf4j)
- -->
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-oxm</artifactId>
- <version>${spring.version}</version>
- <!--
- Required dependency for getting rid of commons logging and use my
- own logging library (in my case I decided to use log4j 2 under slf4j)
- -->
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
-
- <!-- Required by spring-webmvc -->
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <version>4.0.0-b01</version>
- <scope>provided</scope>
- </dependency>
-
- <!--
- Jackson JSON Processor, required by spring-webmvc. See messageConverters
- in rest-config.xml
- -->
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>2.6.4</version>
- </dependency>
-
- <!--
- Required by spring-context for using JSR-303. See LocalValidatorFactoryBean
- in rest-config.xml
- -->
- <dependency>
- <groupId>javax.validation</groupId>
- <artifactId>validation-api</artifactId>
- <version>1.1.0.Final</version>
- </dependency>
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-validator</artifactId>
- <version>5.2.2.Final</version>
- </dependency>
-
-
- <!-- Unitary and integration tests -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>${spring.version}</version>
- <scope>test</scope>
- <!--
- Required dependency for getting rid of commons logging and use my
- own logging library (in my case I decided to use log4j 2 under slf4j)
- -->
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>com.icegreen</groupId>
- <artifactId>greenmail</artifactId>
- <version>1.5.0</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <build>
-
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>2.19.1</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-failsafe-plugin</artifactId>
- <version>2.19.1</version>
- </plugin>
- </plugins>
- </pluginManagement>
-
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- <encoding>${project.build.sourceEncoding}</encoding>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-resources-plugin</artifactId>
- <version>2.7</version>
- <configuration>
- <encoding>${project.build.sourceEncoding}</encoding>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <version>2.6</version>
- <configuration>
- <archive>
- <manifestEntries>
- <Specification-Title>${project.description}</Specification-Title>
- <Specification-Version>${project.version}</Specification-Version>
- <Specification-Vendor>${project.organization.name}</Specification-Vendor>
- <Implementation-Title>${project.description}</Implementation-Title>
- <Implementation-Version>${project.version}</Implementation-Version>
- <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
- </manifestEntries>
- </archive>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>**/*IntegrationTest.java</exclude>
- </excludes>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-failsafe-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>integration-test</goal>
- <goal>verify</goal>
- </goals>
- </execution>
- </executions>
- <configuration>
- <includes>
- <include>**/*IntegrationTest.java</include>
- </includes>
- </configuration>
- </plugin>
- </plugins>
- </build>
-</project>