├── .bowerrc ├── src └── main │ ├── webapp │ ├── app │ │ ├── app.js │ │ ├── filters.js │ │ ├── services.js │ │ └── controllers.js │ ├── assets │ │ └── css │ │ │ └── style.css │ └── WEB-INF │ │ ├── web.xml │ │ └── views │ │ └── ideas.jsp │ ├── java │ └── be │ │ └── g00glen00b │ │ ├── repository │ │ └── IdeaRepository.java │ │ ├── aspects │ │ ├── NotifyClients.java │ │ └── NotifyAspect.java │ │ ├── service │ │ ├── IdeaService.java │ │ └── impl │ │ │ └── IdeaServiceImpl.java │ │ ├── model │ │ └── Idea.java │ │ ├── config │ │ ├── WebSocketAppConfig.java │ │ ├── WebAppConfig.java │ │ └── AppConfig.java │ │ ├── dto │ │ └── IdeaDto.java │ │ └── controller │ │ └── IdeaController.java │ └── resources │ └── META-INF │ └── persistence.xml ├── .gitignore ├── bower.json ├── README.md └── pom.xml /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/main/webapp/libs", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/main/webapp/app/app.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp", [ "myApp.controllers", "myApp.services", "myApp.filters" ]); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .settings 3 | .project 4 | target/ 5 | src/main/webapp/libs/ 6 | .externalToolBuilders 7 | -------------------------------------------------------------------------------- /src/main/webapp/assets/css/style.css: -------------------------------------------------------------------------------- 1 | .ui.list .item .red.description { 2 | color: #A95252; 3 | } 4 | 5 | .ui.form textarea { 6 | min-height: 0; 7 | height: auto; 8 | } 9 | 10 | .ui.list .item .content .description p { 11 | margin: 0; 12 | } -------------------------------------------------------------------------------- /src/main/webapp/app/filters.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp.filters", []).filter("markdown", function($sce) { 2 | var converter = new Showdown.converter(); 3 | return function(value) { 4 | var html = converter.makeHtml(value || ''); 5 | return $sce.trustAsHtml(html); 6 | }; 7 | }); -------------------------------------------------------------------------------- /src/main/webapp/app/services.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp.services", [ "ngResource" ]).factory("Idea", function($resource) { 2 | return $resource("./ideas/:id", { 3 | id: '@id' 4 | }, { 5 | update: { 6 | method: "PUT" 7 | }, 8 | remove: { 9 | method: "DELETE" 10 | } 11 | }); 12 | }); -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/repository/IdeaRepository.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import be.g00glen00b.dto.IdeaDto; 6 | 7 | public interface IdeaRepository extends JpaRepository { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spring-live-updates", 3 | "version": "0.0.1-SNAPSHOT", 4 | "dependencies": { 5 | "angular": "latest", 6 | "angular-resource": "latest", 7 | "jquery": "latest", 8 | "semantic-ui": "latest", 9 | "sockjs": "latest", 10 | "stomp-websocket": "latest", 11 | "showdown": "latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/aspects/NotifyClients.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.aspects; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface NotifyClients { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | be.g00glen00b.dto.IdeaDto 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/service/IdeaService.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.service; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import be.g00glen00b.model.Idea; 8 | 9 | public interface IdeaService { 10 | 11 | List getIdeas(); 12 | 13 | @Transactional 14 | Idea addIdea(Idea idea); 15 | 16 | @Transactional 17 | Idea updateIdea(Idea idea); 18 | 19 | @Transactional 20 | void deleteIdea(Idea idea); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/model/Idea.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.model; 2 | 3 | public class Idea { 4 | 5 | private int id; 6 | 7 | private String title; 8 | 9 | private String description; 10 | 11 | private long votes; 12 | 13 | public Idea() { 14 | super(); 15 | } 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public String getTitle() { 26 | return title; 27 | } 28 | 29 | public void setTitle(String title) { 30 | this.title = title; 31 | } 32 | 33 | public String getDescription() { 34 | return description; 35 | } 36 | 37 | public void setDescription(String description) { 38 | this.description = description; 39 | } 40 | 41 | public long getVotes() { 42 | return votes; 43 | } 44 | 45 | public void setVotes(long votes) { 46 | this.votes = votes; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/aspects/NotifyAspect.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.aspects; 2 | 3 | import java.util.Date; 4 | 5 | import org.aspectj.lang.annotation.After; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Pointcut; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.messaging.simp.SimpMessagingTemplate; 10 | 11 | @Aspect 12 | public class NotifyAspect { 13 | 14 | @Autowired 15 | private SimpMessagingTemplate template; 16 | 17 | private static final String WEBSOCKET_TOPIC = "/topic/notify"; 18 | 19 | @Pointcut("@annotation(be.g00glen00b.aspects.NotifyClients)") 20 | public void notifyPointcut() {} 21 | 22 | @Pointcut("execution(* be.g00glen00b.controller.**.*(..))") 23 | public void methodPointcut() {} 24 | 25 | @After("methodPointcut() && notifyPointcut()") 26 | public void notifyClients() throws Throwable { 27 | template.convertAndSend(WEBSOCKET_TOPIC, new Date()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/config/WebSocketAppConfig.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; 6 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 7 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class WebSocketAppConfig extends AbstractWebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void configureMessageBroker(MessageBrokerRegistry config) { 15 | config.enableSimpleBroker("/topic"); 16 | config.setApplicationDestinationPrefixes("/app"); 17 | } 18 | 19 | @Override 20 | public void registerStompEndpoints(StompEndpointRegistry registry) { 21 | registry.addEndpoint("/notify").withSockJS(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SpringServlet 6 | org.springframework.web.servlet.DispatcherServlet 7 | 8 | contextClass 9 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 10 | 11 | 12 | contextConfigLocation 13 | 14 | be.g00glen00b.config.AppConfig, be.g00glen00b.config.WebAppConfig, be.g00glen00b.config.WebSocketAppConfig 15 | 16 | 17 | true 18 | 19 | 20 | 21 | SpringServlet 22 | / 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/dto/IdeaDto.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | 11 | @Entity 12 | public class IdeaDto implements Serializable { 13 | 14 | private static final long serialVersionUID = -6809049173391335091L; 15 | 16 | @Id 17 | @GeneratedValue(strategy=GenerationType.IDENTITY) 18 | private int id; 19 | 20 | @Column 21 | private String title; 22 | 23 | @Column 24 | private String description; 25 | 26 | @Column 27 | private long votes; 28 | 29 | public IdeaDto() { 30 | super(); 31 | } 32 | 33 | public int getId() { 34 | return id; 35 | } 36 | 37 | public void setId(int id) { 38 | this.id = id; 39 | } 40 | 41 | public String getTitle() { 42 | return title; 43 | } 44 | 45 | public void setTitle(String title) { 46 | this.title = title; 47 | } 48 | 49 | public String getDescription() { 50 | return description; 51 | } 52 | 53 | public void setDescription(String description) { 54 | this.description = description; 55 | } 56 | 57 | public long getVotes() { 58 | return votes; 59 | } 60 | 61 | public void setVotes(long votes) { 62 | this.votes = votes; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/service/impl/IdeaServiceImpl.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.service.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.dozer.Mapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import be.g00glen00b.dto.IdeaDto; 12 | import be.g00glen00b.model.Idea; 13 | import be.g00glen00b.repository.IdeaRepository; 14 | import be.g00glen00b.service.IdeaService; 15 | 16 | @Service 17 | public class IdeaServiceImpl implements IdeaService { 18 | 19 | @Autowired 20 | private IdeaRepository repo; 21 | 22 | @Autowired 23 | private Mapper mapper; 24 | 25 | public List getIdeas() { 26 | List list = repo.findAll(); 27 | List out = new ArrayList(); 28 | for (IdeaDto dto : list) { 29 | out.add(mapper.map(dto, Idea.class)); 30 | } 31 | return out; 32 | } 33 | 34 | @Transactional 35 | @Override 36 | public Idea addIdea(Idea idea) { 37 | IdeaDto dto = mapper.map(idea, IdeaDto.class); 38 | return mapper.map(repo.saveAndFlush(dto), Idea.class); 39 | } 40 | 41 | @Transactional 42 | @Override 43 | public Idea updateIdea(Idea idea) { 44 | IdeaDto dto = repo.findOne(idea.getId()); 45 | dto.setDescription(idea.getDescription()); 46 | dto.setVotes(idea.getVotes()); 47 | dto.setTitle(idea.getTitle()); 48 | return mapper.map(repo.saveAndFlush(dto), Idea.class); 49 | } 50 | 51 | @Transactional 52 | @Override 53 | public void deleteIdea(Idea idea) { 54 | repo.delete(idea.getId()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/webapp/app/controllers.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp.controllers", []).controller("ideaCtrl", function($scope, Idea) { 2 | 3 | $scope.model = { 4 | ideas: Idea.query(), 5 | newIdea: { 6 | title: null, 7 | description: null 8 | } 9 | }; 10 | 11 | $scope.initiator = false; 12 | 13 | $scope.socket = { 14 | client: null, 15 | stomp: null 16 | }; 17 | 18 | $scope.add = function() { 19 | $scope.initiator = true; 20 | var idea = new Idea(); 21 | idea.description = $scope.model.newIdea.description; 22 | idea.title = $scope.model.newIdea.title; 23 | idea.votes = 0; 24 | idea.$save(function(response) { 25 | $scope.model.ideas.push(response); 26 | }); 27 | $scope.model.newIdea.title = ''; 28 | $scope.model.newIdea.description = ''; 29 | }; 30 | 31 | $scope.remove = function(/** Idea */ idea, /** Integer */ index) { 32 | $scope.initiator = true; 33 | $scope.model.ideas.splice(index, 1); 34 | idea.$remove(); 35 | }; 36 | 37 | $scope.addVotes = function(/** Idea */ idea, /** Integer */ votes) { 38 | $scope.initiator = true; 39 | idea.votes += votes; 40 | idea.$update(); 41 | }; 42 | 43 | $scope.notify = function(/** Message */ message) { 44 | if (!$scope.initiator) { 45 | Idea.query(function(ideas) { 46 | $scope.model.ideas = ideas; 47 | }); 48 | } 49 | $scope.initiator = false; 50 | }; 51 | 52 | $scope.reconnect = function() { 53 | setTimeout($scope.initSockets, 10000); 54 | }; 55 | 56 | $scope.initSockets = function() { 57 | $scope.socket.client = new SockJS('/spring-live-updates/notify'); 58 | $scope.socket.stomp = Stomp.over($scope.socket.client); 59 | $scope.socket.stomp.connect({}, function() { 60 | $scope.socket.stomp.subscribe("/topic/notify", $scope.notify); 61 | }); 62 | $scope.socket.client.onclose = $scope.reconnect; 63 | }; 64 | 65 | $scope.initSockets(); 66 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building real time applications using Spring, AngularJS and WebSockets 2 | This application demonstrates how you can write real time applications using Spring 4 and AngularJS. The tutorial to build this application can be found at [http://g00glen00b.be/spring-websockets](http://g00glen00b.be/spring-websockets). 3 | 4 | ## Installation 5 | You need a JSR-356 compliant web container (like Tomcat 8) and at least Java 7. For building the application you need [Maven](http://maven.apache.org) and [Bower](http://bower.io). For Bower you also have to install Node.js (and NPM). 6 | 7 | Building the application can be done by using Maven and Bower. The commands that have to be executed are: 8 | 9 | `bower install` 10 | 11 | `mvn install` 12 | 13 | ## Frameworks 14 | 15 | ### Spring framework 16 | The application is built using [Spring 4](http://spring.io). Spring 4 offers WebSocket integration using a SockJS compliant WebSocket server. 17 | 18 | Spring is also used to to divide our application using the MVC pattern and **spring-data-jpa** for the data access layer. 19 | 20 | Cross cutting concerns like pushing notifications to the client are made using Spring AOP. 21 | 22 | ### Dozer 23 | Entities and model objects are mapped using [Dozer](http://dozer.sourceforge.net). Dozer allows you to (deep) map objects one to one. In this case it's used to translate our entity to a model object. 24 | 25 | ### Bower 26 | Bower is a front-end package manager, in this example I'm using it to import the various front-end libraries we need such as: 27 | 28 | * AngularJS 29 | * Showdown (markdown rendering) 30 | * SockJS and STOMP (WebSocket connection) 31 | * Semantic UI (UI library) 32 | 33 | ### AngularJS 34 | AngularJS allows you to use the MVC pattern on the client (browser) so no DOM interaction is actually needed. It also offers integration with RESTful webservices through the **angular-resource** project. 35 | 36 | ### Semantic UI 37 | [Semantic UI](http://semantic-ui.com) is a UI library (just like the popular Twitter Bootstrap), but in my opinion it has a pretty good naming convention which also makes it easier to understand and remember. 38 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/controller/IdeaController.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | 15 | import be.g00glen00b.aspects.NotifyClients; 16 | import be.g00glen00b.model.Idea; 17 | import be.g00glen00b.service.IdeaService; 18 | 19 | @Controller 20 | @RequestMapping("/") 21 | public class IdeaController { 22 | 23 | @Autowired 24 | private IdeaService service; 25 | 26 | @RequestMapping(method = RequestMethod.GET) 27 | public String viewIdeas() { 28 | return "ideas"; 29 | } 30 | 31 | @RequestMapping(value = "/ideas", method = RequestMethod.GET) 32 | public @ResponseBody List getIdeas() { 33 | return service.getIdeas(); 34 | } 35 | 36 | @NotifyClients 37 | @RequestMapping(value = "/ideas/{id}", method = RequestMethod.PUT) 38 | public @ResponseBody Idea update(@PathVariable int id, @RequestBody Idea idea) { 39 | idea.setId(id); 40 | Idea out = service.updateIdea(idea); 41 | return out; 42 | } 43 | 44 | @NotifyClients 45 | @RequestMapping(value = "/ideas", method = RequestMethod.POST) 46 | public @ResponseBody Idea add(@RequestBody Idea idea) { 47 | Idea out = service.addIdea(idea); 48 | return out; 49 | } 50 | 51 | @NotifyClients 52 | @RequestMapping(value = "/ideas/{id}", method = RequestMethod.DELETE) 53 | @ResponseStatus(HttpStatus.NO_CONTENT) 54 | public void delete(@PathVariable int id) { 55 | Idea task = new Idea(); 56 | task.setId(id); 57 | service.deleteIdea(task); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/config/WebAppConfig.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 10 | import org.springframework.web.servlet.mvc.WebContentInterceptor; 11 | import org.springframework.web.servlet.view.InternalResourceViewResolver; 12 | 13 | @Configuration 14 | @EnableWebMvc 15 | @ComponentScan(basePackages = { "be.g00glen00b.controller" }) 16 | public class WebAppConfig extends WebMvcConfigurerAdapter { 17 | 18 | @Bean 19 | public InternalResourceViewResolver getInternalResourceViewResolver() { 20 | InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 21 | resolver.setPrefix("/WEB-INF/views/"); 22 | resolver.setSuffix(".jsp"); 23 | return resolver; 24 | } 25 | 26 | @Bean 27 | public WebContentInterceptor webContentInterceptor() { 28 | WebContentInterceptor interceptor = new WebContentInterceptor(); 29 | interceptor.setCacheSeconds(0); 30 | interceptor.setUseExpiresHeader(true);; 31 | interceptor.setUseCacheControlHeader(true); 32 | interceptor.setUseCacheControlNoStore(true); 33 | 34 | return interceptor; 35 | } 36 | 37 | @Override 38 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 39 | registry.addResourceHandler("/libs/**").addResourceLocations("/libs/"); 40 | registry.addResourceHandler("/app/**").addResourceLocations("/app/"); 41 | registry.addResourceHandler("/assets/**").addResourceLocations("/assets/"); 42 | } 43 | 44 | @Override 45 | public void addInterceptors(InterceptorRegistry registry) { 46 | registry.addInterceptor(webContentInterceptor()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/ideas.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Spring - Live updates 7 | 8 | 9 | 10 | 11 |
12 |

13 | 14 |
15 | Ideas 16 |
View all posted ideas
17 |
18 |

19 |
20 | 21 |

First!

22 | It seems you're the first one using this web application, start by posting some new ideas. 23 |
24 |
25 |
26 |
27 | {{idea.votes}} votes 28 |
29 | 30 | 31 | 32 |
33 |
34 |
35 |
{{idea.title}}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/java/be/g00glen00b/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package be.g00glen00b.config; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.dozer.DozerBeanMapper; 6 | import org.dozer.Mapper; 7 | import org.dozer.loader.api.BeanMappingBuilder; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.ComponentScan; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 12 | import org.springframework.context.annotation.FilterType; 13 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 14 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 15 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 16 | import org.springframework.orm.jpa.JpaTransactionManager; 17 | import org.springframework.orm.jpa.JpaVendorAdapter; 18 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 19 | import org.springframework.orm.jpa.vendor.Database; 20 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.transaction.annotation.EnableTransactionManagement; 23 | 24 | import be.g00glen00b.aspects.NotifyAspect; 25 | import be.g00glen00b.dto.IdeaDto; 26 | import be.g00glen00b.model.Idea; 27 | 28 | @Configuration 29 | @ComponentScan(basePackages = { "be.g00glen00b" }, excludeFilters = { @ComponentScan.Filter(value = Controller.class, type = FilterType.ANNOTATION) }) 30 | @EnableJpaRepositories(basePackages = { "be.g00glen00b.repository" }) 31 | @EnableTransactionManagement 32 | @EnableAspectJAutoProxy 33 | public class AppConfig { 34 | 35 | @Bean 36 | public DataSource dataSource() { 37 | return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build(); 38 | } 39 | 40 | @Bean 41 | public JpaVendorAdapter jpaVendorAdapter() { 42 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 43 | adapter.setShowSql(true); 44 | adapter.setGenerateDdl(true); 45 | adapter.setDatabase(Database.HSQL); 46 | return adapter; 47 | } 48 | 49 | @Bean 50 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws ClassNotFoundException { 51 | LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean(); 52 | factoryBean.setDataSource(dataSource()); 53 | factoryBean.setPersistenceUnitName("ideas"); 54 | factoryBean.setJpaVendorAdapter(jpaVendorAdapter()); 55 | 56 | return factoryBean; 57 | } 58 | 59 | @Bean 60 | public JpaTransactionManager transactionManager() throws ClassNotFoundException { 61 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 62 | transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); 63 | 64 | return transactionManager; 65 | } 66 | 67 | @Bean 68 | public BeanMappingBuilder beanMappingBuilder() { 69 | BeanMappingBuilder builder = new BeanMappingBuilder() { 70 | protected void configure() { 71 | mapping(IdeaDto.class, Idea.class); 72 | } 73 | }; 74 | 75 | return builder; 76 | } 77 | 78 | @Bean 79 | public Mapper mapper() { 80 | DozerBeanMapper mapper = new DozerBeanMapper(); 81 | mapper.addMapping(beanMappingBuilder()); 82 | 83 | return mapper; 84 | } 85 | 86 | @Bean 87 | public NotifyAspect notifyAspect() { 88 | return new NotifyAspect(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | be.g00glen00b 5 | spring-live-updates 6 | war 7 | 0.0.1-SNAPSHOT 8 | Spring WebSockets app 9 | http://g00glen00b.be/spring-websockets 10 | 11 | 12 | 4.0.2.RELEASE 13 | 2.3.0 14 | 1.7.4 15 | 16 | 17 | 18 | spring-live-updates 19 | 20 | 21 | 22 | 23 | maven-compiler-plugin 24 | 3.1 25 | 26 | 1.7 27 | 1.7 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework 36 | spring-webmvc 37 | ${spring.version} 38 | 39 | 40 | org.springframework 41 | spring-messaging 42 | ${spring.version} 43 | 44 | 45 | org.springframework 46 | spring-websocket 47 | ${spring.version} 48 | 49 | 50 | org.springframework 51 | spring-aop 52 | ${spring.version} 53 | 54 | 55 | org.springframework.data 56 | spring-data-jpa 57 | 1.5.0.RELEASE 58 | 59 | 60 | 61 | org.aspectj 62 | aspectjweaver 63 | ${aspectj.version} 64 | 65 | 66 | org.aspectj 67 | aspectjrt 68 | ${aspectj.version} 69 | 70 | 71 | 72 | org.hibernate 73 | hibernate-entitymanager 74 | 4.3.3.Final 75 | 76 | 77 | org.hsqldb 78 | hsqldb 79 | 2.3.2 80 | 81 | 82 | 83 | net.sf.dozer 84 | dozer 85 | 5.4.0 86 | 87 | 88 | 89 | com.fasterxml.jackson.core 90 | jackson-core 91 | ${jackson.version} 92 | 93 | 94 | com.fasterxml.jackson.core 95 | jackson-annotations 96 | ${jackson.version} 97 | 98 | 99 | com.fasterxml.jackson.core 100 | jackson-databind 101 | ${jackson.version} 102 | 103 | 104 | 105 | javax.websocket 106 | javax.websocket-api 107 | 1.0 108 | provided 109 | 110 | 111 | javax.servlet 112 | javax.servlet-api 113 | 3.1.0 114 | provided 115 | 116 | 117 | javax.servlet 118 | jstl 119 | 1.2 120 | 121 | 122 | This application demonstrates how you can write real time applications using Spring 4 and AngularJS. 123 | 124 | --------------------------------------------------------------------------------