Monday, December 10, 2012

Spring Social with JavaConfig (Part 1)

In this tutorial, we will create an application that can post messages and retrieve profile information from Facebook and Twitter. We will use Spring Social to implement these features. To secure our application we will use Spring Security, and to manage our views, we will use Thymeleaf.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Dependencies

These are the main Maven dependencies:
  • Spring 3.2.0.RELEASE
  • Spring Data JPA 1.2.0.RELEASE
  • Spring Security 3.1.3.RELEASE
  • Thymeleaf 2.0.14
  • Hibernate 3.6.3.Final
  • See pom.xml for full details

Required Tools

These are the minimum required tools:
  • Git
  • Maven 3.0.4
  • MySQL
  • Eclipse IDE or SpringSource Tool Suite (STS)

GitHub Repository

There are two versions of the application: a JavaConfig-based and an XML config-based app. Both versions are identical in their feature set.

Functional Specs


Our application's requirements are the following:
  • Post to Facebook and Twitter
  • Retrieve profile information from Facebook and Twitter
  • Secure the application
  • Allow login and creation of new users
  • Create a page for managing users

Here's our Use Case diagrams:


[User]-(Post to Facebook)
[User]-(Post to Twitter)
[User]-(Retrieve info from Facebook)
[User]-(Retrieve info from Twitter)
[User]-(Sign in)
[User]-(Sign up)

//http://yuml.me/



[Admin]-(Edit users)
[Admin]-(Delete users)
[Admin]-(Add users)

//http://yuml.me/

Screenshots


Before we proceed, let's preview some screenshots of our application:

Sign in page


Sign up


Facebook Profile


Twitter Profile


Manage Users


Post to Facebook


Tweet to Tweeter


Connect to Social Site


Connected to Social Site



Next

In the next section, we will show how to generate the OAuth secret keys for Facebook and Twitter. Click here to proceed.

StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 1) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 2)

Review

In the previous section, we have discussed the functional requirements of our application. In this section we will study how to generate OAuth keys for Facebook and Twitter. These are required so that Spring Social can communicate with these social media sites.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Generate OAuth keys

Facebook

To generate a Facebook secret key, you need to sign-up for a Facebook account first. Once you have an account, follow these steps:
  1. Open a browser
  2. Visit https://developers.facebook.com/apps
  3. Click on Create New App
  4. Fill-in the App Name
  5. You will be redirected to the Basic settings page
  6. Now copy the App ID value. This is your client ID
  7. Then copy the App Secret value. This is your client secret

Note: The values need to be stored in the spring.properties file (see Part 5).

On my sample app, here's the Basic settings page. I've purposely changed the App ID and App Secret values:


Twitter

To generate a Twitter secret key, you need to sign-up for a Twitter account first. Once you have an account, follow these steps:
  1. Open a browser
  2. Visit https://dev.twitter.com/
  3. Visit the My applications page at https://dev.twitter.com/apps
  4. Click on Create a new application
  5. Fill-in the Name
  6. Fill-in the Description
  7. Fill-in the Website (You will need to invent a fictitious URL)
  8. You will be redirected to the Details tab of your new application
  9. Now copy the Consumer key value. This is your client ID
  10. Then copy the Consumer secret value. This is your client secret

Note: The values need to be stored in the spring.properties file (see Part 5).

On my sample app, here's the Details tab. I've purposely changed the Consumer key and Consumer secret values:



Next

In the next section, we will setup the Spring Social-related configuration through JavaConfig. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 3)

Review

In the previous section, we have shown the steps on how to generate and retrieve the OAuth secret keys from Facebook and Twitter. In this section we will setup the Spring Social configuration settings through JavaConfig.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Spring Social configuration


What is Spring Social?

Spring Social is an extension of the Spring Framework that allows you to connect your applications with Software-as-a-Service (SaaS) providers such as Facebook and Twitter.

Features:
  • An extensible service provider framework that greatly simplifies the process of connecting local user accounts to hosted provider accounts.
  • A connect controller that handles the authorization flow between your Java/Spring web application, a service provider, and your users.
  • Java bindings to popular service provider APIs such as Facebook, Twitter, LinkedIn, TripIt, and GitHub.
  • A sign-in controller that enables users to authenticate with your application by signing in through a service provider.
Source: http://www.springsource.org/spring-social

Here's our Spring Social configuration:

SocialConfig.java
package org.krams.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.support.ConnectionFactoryRegistry;
import org.springframework.social.connect.web.ConnectController;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.twitter.connect.TwitterConnectionFactory;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration
public class SocialConfig {
@Autowired
private Environment env;
@Autowired
private DataSource dataSource;
@Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(
env.getRequiredProperty("facebook.clientId"),
env.getRequiredProperty("facebook.clientSecret")));
registry.addConnectionFactory(new TwitterConnectionFactory(
env.getRequiredProperty("twitter.consumerKey"),
env.getRequiredProperty("twitter.consumerSecret")));
return registry;
}
@Bean
public TextEncryptor textEncryptor() {
return Encryptors.noOpText();
}
@Bean
public UsersConnectionRepository usersConnectionRepository() {
return new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator(),
textEncryptor());
}
@Bean
@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public ConnectionRepository connectionRepository() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null)
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
return usersConnectionRepository().createConnectionRepository(authentication.getName());
}
@Bean
public ConnectController connectController() {
ConnectController controller = new ConnectController(connectionFactoryLocator(), connectionRepository());
controller.setApplicationUrl(env.getRequiredProperty("application.url"));
return controller;
}
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
return filter;
}
}


Let me explain the contents of this configuration:

  • We have autowired the environment properties and the datasource
  • We have declared a ConnectionFactoryLocator which allows us to register connections to Facebook and Twitter. Notice how we passed the OAuth secret IDs and secret keys to the locator
  • We've declared a TextEncryptor for encrypting strings. This is required by Spring Social's JdbcUsersConnectionRepository
  • JdbcUsersConnectionRepository is used for persisting connections to a database through JDBC
  • ConnectionRepository allows a specific user to save and retrieve connections. We need to use this in conjunction with Spring Security because it provides us ready-made authenticated users. Notice how we assigned the current authenticated user
  • ConnectController is a controller for managing the connection flow to social media sites
  • HiddenHttpMethodFilter is required by Spring Social so that users can disconnect from social media sites. The filter needs to be declared in the web.xml or ApplicationInitializer

Note: If you need an in-depth explanation of each classes, please see the official Spring Social docs

Next

In the next section, we will focus on Spring Security-related configuration. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 3) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 4)

Review

In the previous section, we have discussed the Spring Social-related configuration. In this section we will focus on Spring Security for securing our application.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Spring Security


What is Spring Security?

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is one of the most mature and widely used Spring projects. Founded in 2003 and actively maintained by SpringSource since, today it is used to secure numerous demanding environments including government agencies, military applications and central banks. It is released under an Apache 2.0 license so you can confidently use it in your projects.

Source: http://www.springsource.org/spring-security

Here's our Spring Security configuration:

SecurityConfig.java
package org.krams.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.filter.DelegatingFilterProxy;
@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class SecurityConfig {
@Bean
public DelegatingFilterProxy springSecurityFilterChain() {
DelegatingFilterProxy filterProxy = new DelegatingFilterProxy();
return filterProxy;
}
}

First, we declare a DelegatingFilterProxy bean using JavaConfig. This allows Spring Security to intercept requests to our application and verify if the required authentication and authorization are met. This bean needs to be registered in the web.xml (or ApplicationInitializer) as a filter (see next section).

Second, we declare the usual XML-based configuration. This allows us to define the intercept-url patterns. Why are we not using JavaConfig here? Because the XML-based configuration is simpler, less-verbose, and easier:

spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<http pattern="/resources" security="none" />
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/signup" access="permitAll"/>
<intercept-url pattern="/logout" access="permitAll"/>
<intercept-url pattern="/error" access="permitAll"/>
<intercept-url pattern="/denied" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/welcome" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/users" access="hasRole('ROLE_ADMIN')"/>
<intercept-url pattern="/fb/profile" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/fb/post" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/tw/profile" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/tw/post" access="hasRole('ROLE_USER')"/>
<form-login login-page="/login"
authentication-failure-url="/login/failure"
default-target-url="/"/>
<access-denied-handler error-page="/denied"/>
<logout invalidate-session="true"
logout-success-url="/logout/success"
logout-url="/logout"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="repositoryBasedUserDetailsService">
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
</beans:beans>


For an in-depth explanation of this configuration, please see my tutorial on Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA

Next

In the next section, we will study the remaining JavaConfig-based configuration. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 4) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 5)

Review

In the previous section, we have discussed the Spring Security-related configurations. In this section we will discuss the remaining configuration of our application.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

JavaConfig

As stated in the introduction, we will be using JavaConfig-based configuration instead of the usual XML config. However, I don't want to alienate our readers who are used to XML. As a result, I've decided to provide both implementations. However, our focus here is still on JavaConfig.

Note: With JavaConfig, we can now omit the ubiquitous web.xml. But in order to that, we need to run a Servlet 3.0 web container. For this tutorial, I have tested the application with Tomcat 7.0.30 (Maven plugin), 7.0.33 (standalone Tomcat), and Jetty 8.1.5.v20120716 (Maven plugin).

ApplicationInitializer.java

The ApplicationInitializer.java is the equivalent of web.xml. Here's where we declare the DispatcherServlet and also we've registered two filters: one for Spring Security and another for Spring Social.

package org.krams.config;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.DispatcherServlet;
public class ApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Load application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationContext.class);
rootContext.setDisplayName("Spring Social Tutorial");
// Add context loader listener
servletContext.addListener(new ContextLoaderListener(rootContext));
// Declare dispatcher servlet
ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
// Register Spring security filter
FilterRegistration.Dynamic springSecurityFilterChain =
servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
// Register Spring Social filter so that we can disconnect from providers
FilterRegistration.Dynamic hiddenHttpMethodFilter =
servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);
hiddenHttpMethodFilter.addMappingForUrlPatterns(null, false, "/*");
}
}


Here's the equivalent XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>Spring Social Tutorial</display-name>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-security.xml
/WEB-INF/applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub


ApplicationContext.java

The ApplicationContext.java contains our main configuration. It's responsible for loading other configurations, either as JavaConfig or XML config.

package org.krams.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@ComponentScan(basePackages = {"org.krams"})
@EnableWebMvc
@Import({DataConfig.class, ThymeleafConfig.class, SocialConfig.class, SecurityConfig.class})
@ImportResource({"classpath:trace-context.xml"})
@PropertySource("classpath:spring.properties")
public class ApplicationContext extends WebMvcConfigurerAdapter {
// Maps resources path to webapp/resources
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
// Provides internationalization of messages
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
// Only needed if we are using @Value and ${...} when referencing properties
// Otherwise @PropertySource is enough by itself
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySources = new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[] {
new ClassPathResource("spring.properties") };
propertySources.setLocations(resources);
propertySources.setIgnoreUnresolvablePlaceholders(true);
return propertySources;
}
}


Let's describe each annotation:
  • @Configuration
    - Marks a class as a JavaConfig
  • @ComponentScan(basePackages = {"org.krams"})
    - Configures scanning of Spring components

    This is equivalent in XML as:
    <context:annotation-config />
    <context:component-scan base-package="org.krams" />

  • @EnableWebMvc

    - Activates Spring's MVC support

    This is equivalent in XML as:
    <mvc:annotation-driven />

  • @Import({DataConfig.class, ThymeleafConfig.class, SocialConfig.class, SecurityConfig.class})
    - This allows us to import JavaConfig-based config. Notice we have imported four configuration classes
  • @ImportResource("classpath:trace-context.xml")
    - This allows us to import XML-based config files. (As a side note why can't we just declare this as a JavaConfig? It turns out there's no direct translation for the trace-context.xml, so we'll have to import it as XML).

    This is equivalent in XML as:
    <import resource="trace-context.xml"/>

  • @PropertySource("classpath:spring.properties")
    - This allows us to import property files
  • @Bean
    - Declares a Spring bean

Here's the equivalent XML configuration:

<?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:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<context:property-placeholder properties-ref="deployProperties" />
<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />
<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
For example @Controller and @Service. Make sure to set the correct base-package -->
<context:component-scan base-package="org.krams" />
<!-- Configures the annotation-driven Spring MVC Controller programming model.
Note that, with Spring 3.0, this tag works in Servlet MVC only! -->
<mvc:annotation-driven />
<!-- Maps resources path to webapp/resources -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<!-- Provides internationalization of messages -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="messages" />
<!-- Imports Spring Social configuration -->
<import resource="spring-social.xml"/>
<!-- Imports datasource configuration -->
<import resource="spring-data.xml"/>
<!-- Imports logging configuration -->
<import resource="trace-context.xml"/>
<bean id="deployProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"
p:location="/WEB-INF/spring.properties" />
</beans>


DataConfig.java

The DataConfig.java contains our Spring Data configuration. This is where we declare our data source, transaction manager, and JPA entity manager.

package org.krams.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.mchange.v2.c3p0.ComboPooledDataSource;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("org.krams.repository")
public class DataConfig extends WebMvcConfigurerAdapter {
@Autowired
private Environment env;
@Bean
public DataSource dataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(env.getRequiredProperty("app.jdbc.driverClassName"));
ds.setJdbcUrl(env.getRequiredProperty("app.jdbc.url"));
ds.setUser(env.getRequiredProperty("app.jdbc.username"));
ds.setPassword(env.getRequiredProperty("app.jdbc.password"));
ds.setAcquireIncrement(5);
ds.setIdleConnectionTestPeriod(60);
ds.setMaxPoolSize(100);
ds.setMaxStatements(50);
ds.setMinPoolSize(10);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendor = new HibernateJpaVendorAdapter();
vendor.setShowSql(false);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceXmlLocation("classpath*:META-INF/persistence.xml");
em.setPersistenceUnitName("hibernatePersistenceUnit");
em.setDataSource(dataSource());
em.setJpaVendorAdapter(vendor);
return em;
}
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
view raw DataConfig.java hosted with ❤ by GitHub


Here's the equivalent XML configuration:

<?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:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<context:property-placeholder properties-ref="deployProperties" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Activate Spring Data JPA repository support -->
<jpa:repositories base-package="org.krams.repository" />
<!-- Declare a datasource that has pooling capabilities-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="${app.jdbc.driverClassName}"
p:jdbcUrl="${app.jdbc.url}"
p:user="${app.jdbc.username}"
p:password="${app.jdbc.password}"
p:acquireIncrement="5"
p:idleConnectionTestPeriod="60"
p:maxPoolSize="100"
p:maxStatements="50"
p:minPoolSize="10" />
<!-- Declare a JPA entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceXmlLocation="classpath*:META-INF/persistence.xml"
p:persistenceUnitName="hibernatePersistenceUnit"
p:dataSource-ref="dataSource"
p:jpaVendorAdapter-ref="hibernateVendor"/>
<!-- Specify our ORM vendor -->
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:showSql="false"/>
<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
</beans>
view raw spring-data.xml hosted with ❤ by GitHub


ThymeleafConfig.java

The ThymeleafConfig.java contains our Thymeleaf configuration. This is where we declare our Thymeleaf view resolver.

package org.krams.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring3.SpringTemplateEngine;
import org.thymeleaf.spring3.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
@Configuration
public class ThymeleafConfig {
@Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setOrder(1);
// Declare virtual paths
resolver.addTemplateAlias("connect/facebookConnect","facebook/connect");
resolver.addTemplateAlias("connect/twitterConnect","twitter/connect");
resolver.addTemplateAlias("connect/facebookConnected","facebook/connected");
resolver.addTemplateAlias("connect/twitterConnected","facebook/connected");
// Disable cache for testing purposes
resolver.setCacheable(false);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
return resolver;
}
}

We've declared some special settings on our Thymeleaf configuration:
// Declare virtual paths
resolver.addTemplateAlias("connect/facebookConnect","facebook/connect");
resolver.addTemplateAlias("connect/twitterConnect","twitter/connect");
resolver.addTemplateAlias("connect/facebookConnected","facebook/connected");
resolver.addTemplateAlias("connect/twitterConnected","facebook/connected");

// Disable cache for testing purposes
resolver.setCacheable(false);

These allows to redirect virtual path requests to specific templates within our application. We need to do this because the ConnectController from Spring Social has its own built-in controller path requests. And we need to redirect the resulting views that matches our template path.

For example, when connecting to Facebook, ConnectController will use the connect/facebookConnect path and you are required to provide a view. Using the addTemplateAlias() method, we can provide a custom view, in this case, the view points to the directory in the WEB-INF/templates/facebook/connect.

Also, we've disabled the caching feature so that we can easily update and test our html pages.

Here's the equivalent XML configuration:

<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<!-- Declare a Thymeleaf resolver -->
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"
p:prefix="/WEB-INF/templates/"
p:suffix=".html"
p:templateMode="HTML5"
p:order="1"
p:cacheable="false"
p:templateAliases-ref="templateAliases"/>
<util:map id="templateAliases">
<entry key="connect/facebookConnect" value="facebook/connect"/>
<entry key="connect/twitterConnect" value="twitter/connect"/>
<entry key="connect/facebookConnected" value="facebook/connected"/>
<entry key="connect/twitterConnected" value="twitter/connected"/>
</util:map>
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"
p:templateResolver-ref="templateResolver" />
<bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"
p:templateEngine-ref="templateEngine"/>
</beans>

SecurityConfig.java

The SecurityConfig.java contains a single bean DelegatingFilterProxy. This is required for Spring Security.

package org.krams.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.filter.DelegatingFilterProxy;
@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class SecurityConfig {
@Bean
public DelegatingFilterProxy springSecurityFilterChain() {
DelegatingFilterProxy filterProxy = new DelegatingFilterProxy();
return filterProxy;
}
}


spring.properties

spring.properties contains the property settings of our application. You need to declare your Facebook and Twitter OAuth settings here. Here's also where you declare your database.

# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc\:mysql\://localhost/spring_social_tutorial
app.jdbc.username=root
app.jdbc.password=
# social properties
facebook.clientId=YOUR-FACEBOOK-CLIENT-ID
facebook.clientSecret=YOUR-FACEBOOK-CLIENT-SECRET
twitter.consumerKey=YOUR-TWITTER-CONSUMER-KEY
twitter.consumerSecret=YOUR-TWITTER-CONSUMER-SECRET
application.url=http://localhost:8080/spring-social-tutorial

messages_en.properties

This is for internationalization of messages. The default language is English. If you need to provide custom language, create a new properties file and replace the values according to the language you've chosen. Please see the Spring documentation for more info on internationalization.

app.name=TestApp
#login.html
login.title=Sign in
login.legend=Sign in
login.username=Username
login.password=Password
login.button=Sign in
login.noaccount=No account? Create one now!
#signup.html
signup.title=Sign up
signup.legend=Sign up
signup.button=Sign up
signup.hasaccount=Already have an account? Sign in!
#welcome.html
welcome.title=Welcome
#post.html
post.title.fb=Post to Facebook
post.title.tw=Post to Twitter
post.form.legend.fb=Update your status:
post.form.legend.tw=Tweet a message:
post.form.action.fb=/fb/post
post.form.action.tw=/tw/post
post.form.submit.fb=Post it!
post.form.submit.tw=Tweet it!
#posted.html
posted.title.fb=Facebook
posted.title.tw=Twitter
posted.success.fb=The following message has been posted:
posted.success.tw=The following message has been tweeted:
posted.failure.fb=Unable to post to your Facebook account!
posted.failure.tw=Unable to post to your Twitter account!
#profile.html
profile.title.fb=Facebook Profile
profile.title.tw=Twitter Profile
profile.h3.fb=Facebook Profile
profile.h3.tw=Twitter Profile
profile.id.fb=Facebook ID:
profile.id.tw=Twitter ID:
profile.name.fb=Name:
profile.name.tw=Screen Name
profile.loc.fb=Email:
profile.loc.tw=Location:
profile.url.fb=Profile Url:
profile.url.tw=Profile Url:
#connect.html
connect.title.fb=Connect to Facebook
connect.title.tw=Connect to Twitter
connect.h3.fb=Connect to Facebook
connect.h3.tw=Connect to Twitter
connect.form.action.fb=/connect/facebook
connect.form.action.tw=/connect/twitter
connect.message.fb=You haven't created any connections with Facebook yet. Click the button to create a connection between your account and your Facebook profile. (You'll be redirected to Facebook where you'll be asked to authorize the connection)
connect.message.tw=You haven't created any connections with Twitter yet. Click the button to create a connection between your account and your Twitter profile. (You'll be redirected to Twitter where you'll be asked to authorize the connection)
connect.button.fb=Connect with Facebook
connect.button.tw=Connect with Tweeter
#connected.html
connected.title.fb=Facebook Connected
connected.title.tw=Twitter Connected
connected.h3.fb=Facebook Connected
connected.h3.tw=Twitter Connected
connected.message.fb=is now connected to your Facebook account. Click the button if you wish to disconnect. Try loading your profile or posting a message to test your connection.
connected.message.tw=is now connected to your Tweeter account. Click the button if you wish to disconnect. Try loading your profile or posting a message to test your connection.
#include.html
disconnect.url.fb=/connect/facebook
disconnect.url.tw=/connect/twitter
disconnect.button.fb=Disconnect from Facebook
disconnect.button.tw=Disconnect from Twitter
#users.html
users.title=User Management
users.table.caption=Site Users
user.id.label=Id
user.firstname.label=First Name
user.lastname.label=Last Name
user.username.label=Username
user.password.label=Password
user.role.label=Role
user.role.1=admin
user.role.2=regular
new.user.table.caption=New User
new.user.button.label=add
update.user.table.caption=Existing User
update.user.button.label=update
error.profile.fb=Failure! An error has occurred while trying to retrieve your profile!
error.profile.tw=Failure! An error has occurred while trying to retrieve your profile!
error.post.fb=Failure! We are unable to process your request!
error.post.tw=Failure! We are unable to process your request!
#errors
error.access.denied=Access denied! You're not allowed to access that!
status.login.failure=Unable to login. Invalid credentials!
status.logout.success=You have successfully logged out!
status.signup.invalid.username.duplicate=Username already exist!
status.signup.invalid.password.notmatching=Passwords do not match!


Next

In the next section, we will discuss the Domain, Service, Controller, and View layers. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 5) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 6)

Review

In the previous section, we have discussed the remaining JavaConfig configurations. In this section, we will discuss the View layer along with Thymeleaf.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

View with Thymeleaf


The goal of this section is not to teach you everything about Thymeleaf but to point out the important sections. If you need a full Thymeleaf documentation, please see the official docs.

What is Thymeleaf?

Thymeleaf is a Java library. It is an XML / XHTML / HTML5 template engine (extensible to other formats) that can work both in web and non-web environments. It is better suited for serving XHTML/HTML5 at the view layer of web applications, but it can process any XML file even in offline environments.

It provides an optional module for integration with Spring MVC, so that you can use it as a complete substitute of JSP in your applications made with this technology, even with HTML5.

The main goal of Thymeleaf is to provide an elegant and well-formed way of creating templates. Its Standard and SpringStandard dialects allow you to create powerful natural templates, that can be correctly displayed by browsers and therefore work also as static prototypes. You can also extend Thymeleaf by developing your own dialects.

Source: Thymeleaf.org

Since we have numerous html pages with duplicate setup, I will focus on the ones that are most instructive.

Facebook profile.html
This displays our Facebook profile information.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header">
<meta charset="utf-8" />
<link rel="stylesheet" href="../../../resources/css/style.css" th:href="@{/resources/css/style.css}" />
<title th:text="#{'profile.title.' + ${source}}">Title</title>
</head>
<body>
<div class="skin">
<div class="content">
<div th:include="include :: menu"></div>
<div th:include="include :: logo"></div>
<div class="profile">
<h3 th:text="#{'profile.h3.' + ${source}}">Your Social Media Profile</h3>
<div class="pic">
<img src="#" th:src="@{'http://graph.facebook.com/' + ${profileInfo.id}} + '/picture'" alt="profile image"/>
<span th:text="${profileInfo.name}">John Smith</span>
</div>
<div class="detail">
<dl>
<dt th:text="#{'profile.id.' + ${source}}">Social ID:</dt>
<dd th:text="${profileInfo.id}">12345678</dd>
<dt th:text="#{'profile.name.' + ${source}}">Social Name:</dt>
<dd th:text="${profileInfo.name}">John Smith</dd>
<dt th:text="#{'profile.loc.' + ${source}}">Social Area:</dt>
<dd th:if="${#strings.isEmpty(profileInfo.email)}" th:text="'no email listed'">john@email.com</dd>
<dt th:text="#{'profile.url.' + ${source}}">Profile Url:</dt>
<dd><a href="#" th:href="@{${profileLink}}" th:text="${profileLink}">http://profile-url</a></dd>
</dl>
</div>
<div th:include="include :: disconnect"></div>
</div>
</div>
<div th:include="include :: footer"></div>
</div>
</body>
</html>
view raw profile.html hosted with ❤ by GitHub


Let's discuss the important attributes
  • The # means to resolve the attribute from the messages bundle
  • The $ means to resolve the attribute from the model
  • The # and $ can be combined together so that messages can be dynamically generated from the model and internationalized from the messages bundle
  • th:href attribute
    <link rel="stylesheet" href="../../../resources/css/style.css"  th:href="@{/resources/css/style.css}" />
    
    Declares a resource relative to the context of the app. When testing the html mockup, the th:href attribute is ignored by the browser. When running the app, Thymeleaf's preprocessor will ignore the value of the href attribute and use the value in the th:ref attribute.
  • th:text attribute
    <title th:text="#{'profile.title.' + ${source}}">Title</title>
    
    Declares the usual title element. When testing the html mockup, the th:text attribute is ignored by the browser. When running the app, Thymeleaf's preprocessor will ignore the value of the title and use the value in the th:text attribute.

    This is a common attribute. So pay attention to this one.
  • th:include attribute
    <div th:include="include :: menu"></div>
    
    Includes an html fragment. The fragment is declared in the include.html
  • th:src attribute
    <img src="#" th:src="@{'http://graph.facebook.com/' +  ${profileInfo.id}} + '/picture'" alt="profile image"/>    
    
    Declares an image element. The attribute th:src is similar with the th:href behavior.

    The value of th:src contains two parts: 'http://graph.facebook.com/' which is a literal string and ${profileInfo.id} is a model attribute. The value is sent by our Spring controller (see below):

    @RequestMapping(value="/profile")
    public String getProfile(ModelMap model) {
    try {
    Facebook facebook = connectionRepository.getPrimaryConnection(Facebook.class).getApi();
    model.addAttribute("profileLink", facebook.userOperations().getUserProfile().getLink());
    model.addAttribute("profileInfo", facebook.userOperations().getUserProfile());
    return "facebook/profile";
    }
    ...
    }

  • th:if attribute
    <dd th:if="${#strings.isEmpty(profileInfo.email)}" th:text="'no email listed'">john@email.com</dd>     
    
    A conditional expression. This states "if the profileInfo.email attribute is empty, print out 'no email listed', but if it's not empty, then print out the value.

Note: To see the remaining html pages, please visit the Github repository. I've omitted them here because most of the Thymeleaf tags we've discussed here are also the same ones we've used on those pages.

login.html
This is the login page. We've shown it here because this is used by Spring Security for rendering the login page.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header">
<meta charset="utf-8" />
<link rel="stylesheet" href="../../../resources/css/style.css" th:href="@{/resources/css/style.css}" />
<title th:text="#{login.title}">Sign in</title>
</head>
<body>
<div class="skin">
<div class="content">
<div th:include="include :: banner"></div>
<div class="login">
<form action="#" th:action="@{/j_spring_security_check}" method="post" >
<fieldset>
<label for="j_username" th:text="#{login.username}">Username</label>
<input id="j_username" name="j_username" type="text"/>
<label for="j_password" th:text="#{login.password}">Password</label>
<input id="j_password" name="j_password" type="password"/>
<button type="submit" th:text="#{login.button}">Submit</button>
</fieldset>
</form>
<p><a href="#" th:href="@{/signup}" th:text="#{login.noaccount}">No account? Create one now!</a></p>
</div>
<p class="status" th:if="${not #strings.isEmpty(status)}" th:text="#{'status.' + ${status}}">Status</p>
</div>
<div th:include="include :: footer"></div>
</div>
</body>
</html>
view raw login.html hosted with ❤ by GitHub


For more info, please see Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA tutorial.

Next

In the next section, we will focus on the Domain, Repository, Service, and Controller classes Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 6) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 7)

Review

In the previous section, we have discussed the View layer along with Thymeleaf. In this section, we will focus on the Domain, Repository, Service, and Controller classes.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Layers

Domain


Our domain layer consists of two simple classes: User.java and Role.java. By annotating these classes with @Entity we're declaring these classes as JPA entities and consequently will be persisted to a database.

The User class contains the following properties: first name, last name, username, role, and password. For the Role class, we only have two values: an admin and a regular user.

package org.krams.domain;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import org.codehaus.jackson.annotate.JsonManagedReference;
@Entity(name="user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@Column(unique=true)
private String username;
private String password;
@JsonManagedReference
@OneToOne(mappedBy="user", cascade={CascadeType.ALL})
private Role role;
public User() {}
...getters/setters
}
view raw User.java hosted with ❤ by GitHub


package org.krams.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import org.codehaus.jackson.annotate.JsonBackReference;
@Entity(name="role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@JsonBackReference
@OneToOne
private User user;
private Integer role;
public Role() {}
...getters/setters
}
view raw Role.java hosted with ❤ by GitHub


Although this is not part of the domain layer, we've included the UserDto here. This DTO is used for transferring user information to the view layer.

package org.krams.response;
import java.io.Serializable;
public class UserDto implements Serializable {
private static final long serialVersionUID = -5488702255320352709L;
private Long id;
private String firstName;
private String lastName;
private String username;
private String password;
private String repassword;
private Integer role;
...getters/setters
}
view raw UserDto.java hosted with ❤ by GitHub


Controller


We have five controllers:
  • AccessController is responsible for managing login and signup requests
  • FacebookController is responsible for handling Facebook requests
  • TwitterController is responsible for handling Twitter requests
  • UserController is responsible for handling User CRUD operations
  • MediatorController simply handles call to the root page

AccessController.java
package org.krams.controller;
import org.krams.domain.Role;
import org.krams.domain.User;
import org.krams.repository.UserRepository;
import org.krams.response.UserDto;
import org.krams.util.RoleUtil;
import org.krams.util.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping
public class AccessController {
@Autowired
private UserRepository userRepository;
@RequestMapping("/login")
public String login() {
return "access/login";
}
@RequestMapping("/denied")
public String denied(ModelMap model) {
model.addAttribute("error", "access.denied");
return "error";
}
@RequestMapping("/login/failure")
public String loginFailure(ModelMap model) {
model.addAttribute("status", "login.failure");
return "access/login";
}
@RequestMapping("/logout/success")
public String logoutSuccess(ModelMap model) {
model.addAttribute("status", "logout.success");
return "access/login";
}
@RequestMapping("/signup")
public String signup() {
return "access/signup";
}
@RequestMapping(value="/signup", method=RequestMethod.POST)
public String createAccount(UserDto dto, ModelMap model) {
if (userRepository.findByUsername(dto.getUsername()) != null) {
model.addAttribute("status", "signup.invalid.username.duplicate");
return "access/signup";
}
if (dto.getPassword().equals(dto.getRepassword()) == false) {
model.addAttribute("status", "signup.invalid.password.notmatching");
return "access/signup";
}
User user = UserMapper.map(dto);
user.setRole(new Role(RoleUtil.ROLE_USER, user));
user = userRepository.save(user);
return "redirect:/";
}
}


FacebookController.java
package org.krams.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.NotConnectedException;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.ModelMap;
@Controller
@RequestMapping("/fb")
public class FacebookController {
@Autowired
private ConnectionRepository connectionRepository;
@ModelAttribute("source")
public String source() {
return "fb";
}
@RequestMapping(value="/profile")
public String getProfile(ModelMap model) {
try {
Facebook facebook = connectionRepository.getPrimaryConnection(Facebook.class).getApi();
model.addAttribute("profileLink", facebook.userOperations().getUserProfile().getLink());
model.addAttribute("profileInfo", facebook.userOperations().getUserProfile());
return "facebook/profile";
} catch (NotConnectedException e) {
return "facebook/connect";
}
}
@RequestMapping(value="/post", method=RequestMethod.GET)
public String composer(ModelMap model) {
try {
connectionRepository.getPrimaryConnection(Facebook.class).getApi();
} catch (NotConnectedException e) {
return "facebook/connect";
}
return "post";
}
@RequestMapping(value="/post", method=RequestMethod.POST)
public String post(String message, ModelMap model) {
try {
Facebook facebook = connectionRepository.getPrimaryConnection(Facebook.class).getApi();
facebook.feedOperations().updateStatus(message);
model.addAttribute("status", "success");
model.addAttribute("message", message);
return "posted";
} catch (Exception e) {
model.addAttribute("status", "failure");
return "posted";
}
}
}


MediatorController.java
package org.krams.controller;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
public class MediatorController {
@RequestMapping
public String getHomePage(ModelMap model) {
model.addAttribute("authname", SecurityContextHolder.getContext().getAuthentication().getName());
return "welcome";
}
}


TwitterController.java
package org.krams.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.NotConnectedException;
import org.springframework.social.twitter.api.Twitter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.ModelMap;
@Controller
@RequestMapping("/tw")
public class TwitterController {
@Autowired
private ConnectionRepository connectionRepository;
@ModelAttribute("source")
public String source() {
return "tw";
}
@RequestMapping(value="/profile")
public String getProfile(ModelMap model) {
try {
Twitter twitter = connectionRepository.getPrimaryConnection(Twitter.class).getApi();
model.addAttribute("profileLink", twitter.userOperations().getUserProfile().getUrl());
model.addAttribute("profileInfo", twitter.userOperations().getUserProfile());
return "twitter/profile";
} catch (NotConnectedException e) {
return "twitter/connect";
}
}
@RequestMapping(value="/post", method=RequestMethod.GET)
public String composer(ModelMap model) {
try {
connectionRepository.getPrimaryConnection(Twitter.class).getApi();
} catch (NotConnectedException e) {
return "twitter/connect";
}
return "post";
}
@RequestMapping(value="/post", method=RequestMethod.POST)
public String post(String message, ModelMap model) {
try {
Twitter twitter = connectionRepository.getPrimaryConnection(Twitter.class).getApi();
twitter.timelineOperations().updateStatus(message);
model.addAttribute("status", "success");
model.addAttribute("message", message);
return "posted";
} catch (Exception e) {
model.addAttribute("status", "failure");
return "posted";
}
}
}


UserController.java
package org.krams.controller;
import java.util.List;
import org.krams.domain.Role;
import org.krams.domain.User;
import org.krams.repository.UserRepository;
import org.krams.response.UserDto;
import org.krams.service.UserService;
import org.krams.util.RoleUtil;
import org.krams.util.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
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.ResponseBody;
import org.springframework.ui.ModelMap;
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository repository;
@Autowired
private UserService service;
@ModelAttribute("allRoles")
public List<Integer> getAllRoles() {
return RoleUtil.roles();
}
@RequestMapping
public String getUsersPage(ModelMap model) {
Pageable pageRequest = new PageRequest(0, 100);
Page<User> users = repository.findAll(pageRequest);
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", new UserDto());
model.addAttribute("usertype", "new");
return "users";
}
@RequestMapping(value="/get", produces="application/json")
public @ResponseBody UserDto get(@RequestBody UserDto user) {
return UserMapper.map(repository.findByUsername(user.getUsername()));
}
@RequestMapping(value="/create", produces="application/json", method=RequestMethod.POST)
public String create(UserDto dto) {
if (dto.getId() != null) {
User existingUser = UserMapper.map(dto);
existingUser.setRole(new Role(dto.getRole(), existingUser));
service.update(existingUser);
} else {
User newUser = UserMapper.map(dto);
newUser.setRole(new Role(dto.getRole(), newUser));
service.create(newUser);
}
return "redirect:/users";
}
@RequestMapping(value="/edit")
public String edit(Long id, ModelMap model) {
Pageable pageRequest = new PageRequest(0, 100);
Page<User> users = repository.findAll(pageRequest);
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", UserMapper.map(repository.findOne(id)));
model.addAttribute("usertype", "update");
return "users";
}
@RequestMapping(value="/delete")
public String delete(Long id) {
User existingUser = new User();
existingUser.setId(id);
service.delete(existingUser);
return "redirect:/users";
}
}


Repository


We have a simple repository. There's nothing much to explain here.

UserRepository.java
package org.krams.repository;
import org.krams.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}


Service


We have two services:
  • UserService is used for handling user-related CRUD operations
  • RepositoryBasedUserDetailsService is used for retrieving user details for authentication purposes

UserService.java
package org.krams.service;
import org.krams.domain.User;
import org.krams.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository repository;
public Boolean create(User user) {
User existingUser = repository.findByUsername(user.getUsername());
if (existingUser != null)
return false;
user.getRole().setUser(user);
User saved = repository.save(user);
if (saved == null)
return false;
return true;
}
public Boolean update(User user) {
User existingUser = repository.findByUsername(user.getUsername());
if (existingUser == null)
return false;
// Only firstName, lastName, and role fields are updatable
existingUser.setFirstName(user.getFirstName());
existingUser.setLastName(user.getLastName());
existingUser.getRole().setRole(user.getRole().getRole());
User saved = repository.save(existingUser);
if (saved == null)
return false;
return true;
}
public Boolean delete(User user) {
User existingUser = repository.findOne(user.getId());
if (existingUser == null)
return false;
repository.delete(existingUser);
User deletedUser = repository.findOne(user.getId());
if (deletedUser != null)
return false;
return true;
}
}


RepositoryBasedUserDetailsService.java
package org.krams.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.krams.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
public class RepositoryBasedUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
/**
* Returns a populated {@link UserDetails} object. The username is first retrieved from
* the database and then mapped to a {@link UserDetails} object.
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
org.krams.domain.User domainUser = userRepository.findByUsername(username);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(
domainUser.getUsername(),
domainUser.getPassword().toLowerCase(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(domainUser.getRole().getRole()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Retrieves a collection of {@link GrantedAuthority} based on a numerical role.
*
* @param role the numerical role
* @return a collection of {@link GrantedAuthority
*/
public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
return authList;
}
/**
* Converts a numerical role to an equivalent list of roles.
*
* @param role the numerical role
* @return list of roles as as a list of {@link String}
*/
public List<String> getRoles(Integer role) {
List<String> roles = new ArrayList<String>();
if (role.intValue() == 1) {
roles.add("ROLE_USER");
roles.add("ROLE_ADMIN");
} else if (role.intValue() == 2) {
roles.add("ROLE_USER");
}
return roles;
}
/**
* Wraps {@link String} roles to {@link SimpleGrantedAuthority} objects.
*
* @param roles {@link String} of roles
* @return list of granted authorities
*/
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}

Next

In the next section, we will study how to build and run our application. We will use Maven, Tomcat, and Jetty to run the app. We'll also study how to import the project in Eclipse. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 7) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Social with JavaConfig (Part 8)

Review


In the previous section, we have discussed the Domain, Repository, Service, Controller layers. In this section, we will build and run our sample application. We will verify if our application is able to communicate with Facebook and Twitter. We will also show how to import the project in Eclipse.


Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Generate OAuth keys
    • Facebook
    • Twitter
  3. Spring Social configuration
  4. Spring Security configuration
  5. JavaConfig
    • ApplicationInitializer.java
    • ApplicationContext.java
    • DataConfig.java
    • ThymeleafConfig.java
    • spring.properties
  6. View with Thymeleaf
  7. Layers
    • Domain
    • Repository
    • Service
    • Controller
  8. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Running the Application


Clone from GitHub

To clone from GitHub, follow these instructions:
  1. Open a Git terminal
  2. Enter the following command:
    git clone https://github.com/krams915/spring-social-javaconfig.git
    This will clone the JavaConfig-based application.

    If you prefer the XML-based version,
    git clone https://github.com/krams915/spring-social-xmlconfig.git

    Remember:

    There are two versions of our application: a JavaConfig-based and an XML config-based app. Both versions are identical in their feature set.

Create the Database

  1. Run MySQL
  2. Create a new database:
    spring_social_tutorial
  3. Import the following SQL files:
    spring_social_tutorial.sql
    JdbcUsersConnectionRepository.sql
    

    These files can be found at the src/main/resources path

Run with Maven and Tomcat 7

Ensure Maven is installed first, and you have created the MySQL database
  1. Open a terminal
  2. Browse to the directory where you've cloned the project
  3. Enter the following command:
    mvn tomcat7:run
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] -------------------------------------------------------------
    [INFO] Building spring-social-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] -------------------------------------------------------------
    [INFO] 
    [INFO] >>> tomcat7-maven-plugin:2.0:run (default-cli) @ spring-social-tutorial >>>
    ...
    ...
    ...
    Dec 10, 2012 9:50:56 AM org.apache.coyote.AbstractProtocol init
    INFO: Initializing ProtocolHandler ["http-bio-8080"]
    Dec 10, 2012 9:50:56 AM org.apache.catalina.core.StandardService startInternal
    INFO: Starting service Tomcat
    Dec 10, 2012 9:50:56 AM org.apache.catalina.core.StandardEngine startInternal
    INFO: Starting Servlet Engine: Apache Tomcat/7.0.30
    Dec 10, 2012 9:51:07 AM org.apache.catalina.core.ApplicationContext log
    INFO: Spring WebApplicationInitializers detected on classpath: [org.krams.config.ApplicationInitializer@73b8cdd5]
    Dec 10, 2012 9:51:08 AM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring root WebApplicationContext
    Dec 10, 2012 9:51:18 AM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring FrameworkServlet 'dispatcher'
    Dec 10, 2012 9:51:18 AM org.apache.coyote.AbstractProtocol start
    INFO: Starting ProtocolHandler ["http-bio-8080"]
    
  5. Open a browser
  6. Visit the entry page:
    http://localhost:8080/spring-social-tutorial
  7. The primary admin credentials are the following:
    username: john
    password: admin

    You can create a new account but by default it doesn't have any admin powers.

Run with Maven and Jetty 8

Ensure Maven is installed first, and you have created the MySQL database.
  1. Open a terminal
  2. Browse to the directory where you've cloned the project
  3. Enter the following command:
    mvn jetty:run
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] -------------------------------------------------------------
    [INFO] Building spring-social-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] -------------------------------------------------------------
    [INFO] 
    [INFO] >>> jetty-maven-plugin:8.1.5.v20120716:run (default-cli) @ spring-social-tutorial >>>
    ...
    ...
    ...
    2012-12-10 09:53:55.980:INFO:/spring-social-tutorial:Initializing Spring FrameworkServlet 'dispatcher'
    2012-12-10 09:53:56.140:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080
    [INFO] Started Jetty Server
    
  5. Open a browser
  6. Visit the entry page:
    http://localhost:8080/spring-social-tutorial
  7. The primary admin credentials are the following:
    username: john
    password: admin

    You can create a new account but by default it doesn't have any admin powers.

Import to Eclipse

Ensure Maven is installed first
  1. Open a terminal
  2. Enter the following command:
    mvn eclipse:eclipse -Dwtpversion=2.0
  3. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] -------------------------------------------------------------
    [INFO] Building spring-social-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] -------------------------------------------------------------
    [INFO] 
    [INFO] >>> maven-eclipse-plugin:2.9:eclipse (default-cli) @ spring-social-tutorial >>>
    ...
    ...
    ... 
    [INFO] -------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] -------------------------------------------------------------
    [INFO] Total time: 9.532s
    [INFO] Finished at: Mon Dec 10 09:55:31 PHT 2012
    [INFO] Final Memory: 17M/81M
    [INFO] -------------------------------------------------------------
    

    This command will add the following files to your project:
    .classpath
    .project
    .settings
    target
    You may have to enable "show hidden files" in your file explorer to view them.
  4. Run Eclipse and import the application as Maven project

Validate with W3C Markup Validation Service

One of the promises of using Thymeleaf is it produces valid HTML pages. Let's test that out using W3C validation service.
  1. Run the application
  2. Open a browser
  3. Visit the following URLs and validate them all:
    http://localhost:8080/spring-social-tutorial/
    http://localhost:8080/spring-social-tutorial/login
    http://localhost:8080/spring-social-tutorial/users
    http://localhost:8080/spring-social-tutorial/fb/profile
    http://localhost:8080/spring-social-tutorial/tw/profile
    http://localhost:8080/spring-social-tutorial/fb/post
    http://localhost:8080/spring-social-tutorial/tw/post
  4. View the HTML source (right-click or go to menu)
  5. Copy the HTML source
  6. Open W3C Markup Validation Service at http://validator.w3.org/#validate_by_input
  7. Paste the HTML source and wait for the validation result

    You should see a similar output:

Conclusion


We've have completed our Spring Social-based application using JavaConfig. We're able to post and retrieve profile information from Facebook and Twitter. To provide authentication and security we've added Spring Security. For managing the view layer, we've integrated Thymeleaf as our template engine. As a bonus, we've also provided an XML-based application.

I hope you've enjoyed this tutorial. Don't forget to check my other tutorials at the Tutorials section.

Revision History

Revision Date Description
1 Dec 10 2012 Uploaded tutorial and GitHub repositories
2 Dec 21 2012 Update to Spring 3.2.0.RELEASE

StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Social with JavaConfig (Part 8) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share