Merge remote-tracking branch 'origin/master'

This commit is contained in:
Alex Theedom 2016-07-18 22:29:43 +01:00
commit 3efa23b6b5
47 changed files with 1725 additions and 20 deletions

View File

@ -29,7 +29,7 @@
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes> <attributes>
<attribute name="owner.project.facets" value="java"/> <attribute name="maven.pomderived" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="output" path="target/classes"/> <classpathentry kind="output" path="target/classes"/>

View File

@ -109,6 +109,31 @@
<version>${mockito.version}</version> <version>${mockito.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<!-- <scope>runtime</scope> -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
<!-- <scope>runtime</scope> --> <!-- some spring dependencies need to compile against jcl -->
</dependency>
<dependency> <!-- needed to bridge to slf4j for projects that use the log4j APIs directly -->
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,36 @@
package com.baeldung.jackson.objectmapper;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baeldung.jackson.objectmapper.dto.Car;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
public class CustomCarDeserializer extends JsonDeserializer<Car> {
private final Logger Logger = LoggerFactory.getLogger(getClass());
public CustomCarDeserializer() {
}
@Override
public Car deserialize(final JsonParser parser, final DeserializationContext deserializer) throws IOException {
final Car car = new Car();
final ObjectCodec codec = parser.getCodec();
final JsonNode node = codec.readTree(parser);
try {
final JsonNode colorNode = node.get("color");
final String color = colorNode.asText();
car.setColor(color);
} catch (final Exception e) {
Logger.debug("101_parse_exeption: unknown json.");
}
return car;
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.jackson.objectmapper;
import com.baeldung.jackson.objectmapper.dto.Car;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class CustomCarSerializer extends JsonSerializer<Car>
{
public CustomCarSerializer() { }
@Override
public void serialize(final Car car, final JsonGenerator jsonGenerator, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("model: ", car.getType());
jsonGenerator.writeEndObject();
}
}

View File

@ -0,0 +1,68 @@
package com.baeldung.jackson.objectmapper;
import com.baeldung.jackson.objectmapper.dto.Car;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
public class TestJavaReadWriteJsonExample {
final String EXAMPLE_JSON = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
final String LOCAL_JSON = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"BMW\" }]";
@Test
public void whenWriteJavaToJson_thanCorrect() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final Car car = new Car("yellow", "renault");
final String carAsString = objectMapper.writeValueAsString(car);
assertThat(carAsString, containsString("yellow"));
assertThat(carAsString, containsString("renault"));
}
@Test
public void whenReadJsonToJava_thanCorrect() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final Car car = objectMapper.readValue(EXAMPLE_JSON, Car.class);
assertNotNull(car);
assertThat(car.getColor(), containsString("Black"));
}
@Test
public void whenReadJsonToJsonNode_thanCorrect() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final JsonNode jsonNode = objectMapper.readTree(EXAMPLE_JSON);
assertNotNull(jsonNode);
assertThat(jsonNode.get("color").asText(), containsString("Black"));
}
@Test
public void whenReadJsonToList_thanCorrect() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final List<Car> listCar = objectMapper.readValue(LOCAL_JSON, new TypeReference<List<Car>>() {
});
for (final Car car : listCar) {
assertNotNull(car);
assertThat(car.getType(), equalTo("BMW"));
}
}
@Test
public void whenReadJsonToMap_thanCorrect() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final Map<String, Object> map = objectMapper.readValue(EXAMPLE_JSON, new TypeReference<Map<String, Object>>() {
});
assertNotNull(map);
for (final String key : map.keySet()) {
assertNotNull(key);
}
}
}

View File

@ -0,0 +1,84 @@
package com.baeldung.jackson.objectmapper;
import com.baeldung.jackson.objectmapper.dto.Car;
import com.baeldung.jackson.objectmapper.dto.Request;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
public class TestSerializationDeserializationFeature {
final String EXAMPLE_JSON = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
final String JSON_CAR = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }";
final String JSON_ARRAY = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"BMW\" }]";
@Test
public void whenFailOnUnkownPropertiesFalse_thanJsonReadCorrectly() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final Car car = objectMapper.readValue(JSON_CAR, Car.class);
final JsonNode jsonNodeRoot = objectMapper.readTree(JSON_CAR);
final JsonNode jsonNodeYear = jsonNodeRoot.get("year");
final String year = jsonNodeYear.asText();
assertNotNull(car);
assertThat(car.getColor(), equalTo("Black"));
assertThat(year, containsString("1970"));
}
@Test
public void whenCustomSerializerDeserializer_thanReadWriteCorrect() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule serializerModule = new SimpleModule("CustomSerializer", new Version(1, 0, 0, null, null, null));
serializerModule.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(serializerModule);
final Car car = new Car("yellow", "renault");
final String carJson = mapper.writeValueAsString(car);
assertThat(carJson, containsString("renault"));
assertThat(carJson, containsString("model"));
final SimpleModule deserializerModule = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
deserializerModule.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(deserializerModule);
final Car carResult = mapper.readValue(EXAMPLE_JSON, Car.class);
assertNotNull(carResult);
assertThat(carResult.getColor(), equalTo("Black"));
}
@Test
public void whenDateFormatSet_thanSerializedAsExpected() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final Car car = new Car("yellow", "renault");
final Request request = new Request();
request.setCar(car);
request.setDatePurchased(new Date());
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
objectMapper.setDateFormat(df);
final String carAsString = objectMapper.writeValueAsString(request);
assertNotNull(carAsString);
assertThat(carAsString, containsString("datePurchased"));
}
@Test
public void whenUseJavaArrayForJsonArrayTrue_thanJsonReadAsArray() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
final Car[] cars = objectMapper.readValue(JSON_ARRAY, Car[].class);
for (final Car car : cars) {
assertNotNull(car);
assertThat(car.getType(), equalTo("BMW"));
}
}
}

View File

@ -0,0 +1,31 @@
package com.baeldung.jackson.objectmapper.dto;
public class Car {
private String color;
private String type;
public Car() {
}
public Car(final String color, final String type) {
this.color = color;
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(final String color) {
this.color = color;
}
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.jackson.objectmapper.dto;
import java.util.Date;
public class Request {
Car car;
Date datePurchased;
public Car getCar() {
return car;
}
public void setCar(final Car car) {
this.car = car;
}
public Date getDatePurchased() {
return datePurchased;
}
public void setDatePurchased(final Date datePurchased) {
this.datePurchased = datePurchased;
}
}

3
jjwt/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
target
*.iml

45
jjwt/README.md Normal file
View File

@ -0,0 +1,45 @@
## JWT Fun
This tutorial walks you through the various features supported by the [JJWT](https://github.com/jwtk/jjwt) library - a fluent interface Java JWT building and parsing library.
### Build and Run
It's super easy to build and exercise this tutorial.
```
mvn clean install
java -jar target/*.jar
```
That's it!
You can hit the home endpoint with your favorite command-line http client. My favorite is: [httpie](https://github.com/jkbrzt/httpie)
`http localhost:8080`
```
Available commands (assumes httpie - https://github.com/jkbrzt/httpie):
http http://localhost:8080/
This usage message
http http://localhost:8080/static-builder
build JWT from hardcoded claims
http POST http://localhost:8080/dynamic-builder-general claim-1=value-1 ... [claim-n=value-n]
build JWT from passed in claims (using general claims map)
http POST http://localhost:8080/dynamic-builder-specific claim-1=value-1 ... [claim-n=value-n]
build JWT from passed in claims (using specific claims methods)
http POST http://localhost:8080/dynamic-builder-compress claim-1=value-1 ... [claim-n=value-n]
build DEFLATE compressed JWT from passed in claims
http http://localhost:8080/parser?jwt=<jwt>
Parse passed in JWT
http http://localhost:8080/parser-enforce?jwt=<jwt>
Parse passed in JWT enforcing the 'iss' registered claim and the 'hasMotorcycle' custom claim
```
The Baeldung post that compliments this repo can be found [here](http://www.baeldung.com/)

65
jjwt/pom.xml Normal file
View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwtfun</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jjwtfun</name>
<description>Exercising the JJWT</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package io.jsonwebtoken.jjwtfun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JJWTFunApplication {
public static void main(String[] args) {
SpringApplication.run(JJWTFunApplication.class, args);
}
}

View File

@ -0,0 +1,21 @@
package io.jsonwebtoken.jjwtfun.config;
import io.jsonwebtoken.jjwtfun.service.SecretService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration
public class CSRFConfig {
@Autowired
SecretService secretService;
@Bean
@ConditionalOnMissingBean
public CsrfTokenRepository jwtCsrfTokenRepository() {
return new JWTCsrfTokenRepository(secretService.getHS256SecretBytes());
}
}

View File

@ -0,0 +1,68 @@
package io.jsonwebtoken.jjwtfun.config;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Date;
import java.util.UUID;
public class JWTCsrfTokenRepository implements CsrfTokenRepository {
private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = CSRFConfig.class.getName().concat(".CSRF_TOKEN");
private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class);
private byte[] secret;
public JWTCsrfTokenRepository(byte[] secret) {
this.secret = secret;
}
@Override
public CsrfToken generateToken(HttpServletRequest request) {
String id = UUID.randomUUID().toString().replace("-", "");
Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds
String token = Jwts.builder()
.setId(id)
.setIssuedAt(now)
.setNotBefore(now)
.setExpiration(exp)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
if (token == null) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
}
}
else {
HttpSession session = request.getSession();
session.setAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME, token);
}
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null || "GET".equals(request.getMethod())) {
return null;
}
return (CsrfToken) session.getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
}
}

View File

@ -0,0 +1,85 @@
package io.jsonwebtoken.jjwtfun.config;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.jjwtfun.service.SecretService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CsrfTokenRepository jwtCsrfTokenRepository;
@Autowired
SecretService secretService;
// ordered so we can use binary search below
private String[] ignoreCsrfAntMatchers = {
"/dynamic-builder-compress",
"/dynamic-builder-general",
"/dynamic-builder-specific",
"/set-secrets"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new JwtCsrfValidatorFilter(), CsrfFilter.class)
.csrf()
.csrfTokenRepository(jwtCsrfTokenRepository)
.ignoringAntMatchers(ignoreCsrfAntMatchers)
.and().authorizeRequests()
.antMatchers("/**")
.permitAll();
}
private class JwtCsrfValidatorFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// NOTE: A real implementation should have a nonce cache so the token cannot be reused
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
if (
// only care if it's a POST
"POST".equals(request.getMethod()) &&
// ignore if the request path is in our list
Arrays.binarySearch(ignoreCsrfAntMatchers, request.getServletPath()) < 0 &&
// make sure we have a token
token != null
) {
// CsrfFilter already made sure the token matched. Here, we'll make sure it's not expired
try {
Jwts.parser()
.setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(token.getToken());
} catch (JwtException e) {
// most likely an ExpiredJwtException, but this will handle any
request.setAttribute("exception", e);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
RequestDispatcher dispatcher = request.getRequestDispatcher("expired-jwt");
dispatcher.forward(request, response);
}
}
filterChain.doFilter(request, response);
}
}
}

View File

@ -0,0 +1,23 @@
package io.jsonwebtoken.jjwtfun.controller;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.jjwtfun.model.JwtResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
public class BaseController {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({SignatureException.class, MalformedJwtException.class, JwtException.class})
public JwtResponse exception(Exception e) {
JwtResponse response = new JwtResponse();
response.setStatus(JwtResponse.Status.ERROR);
response.setMessage(e.getMessage());
response.setExceptionType(e.getClass().getName());
return response;
}
}

View File

@ -0,0 +1,108 @@
package io.jsonwebtoken.jjwtfun.controller;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.compression.CompressionCodecs;
import io.jsonwebtoken.jjwtfun.model.JwtResponse;
import io.jsonwebtoken.jjwtfun.service.SecretService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController
public class DynamicJWTController extends BaseController {
@Autowired
SecretService secretService;
@RequestMapping(value = "/dynamic-builder-general", method = POST)
public JwtResponse dynamicBuilderGeneric(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
String jws = Jwts.builder()
.setClaims(claims)
.signWith(
SignatureAlgorithm.HS256,
secretService.getHS256SecretBytes()
)
.compact();
return new JwtResponse(jws);
}
@RequestMapping(value = "/dynamic-builder-compress", method = POST)
public JwtResponse dynamicBuildercompress(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
String jws = Jwts.builder()
.setClaims(claims)
.compressWith(CompressionCodecs.DEFLATE)
.signWith(
SignatureAlgorithm.HS256,
secretService.getHS256SecretBytes()
)
.compact();
return new JwtResponse(jws);
}
@RequestMapping(value = "/dynamic-builder-specific", method = POST)
public JwtResponse dynamicBuilderSpecific(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
JwtBuilder builder = Jwts.builder();
claims.forEach((key, value) -> {
switch (key) {
case "iss":
ensureType(key, value, String.class);
builder.setIssuer((String) value);
break;
case "sub":
ensureType(key, value, String.class);
builder.setSubject((String) value);
break;
case "aud":
ensureType(key, value, String.class);
builder.setAudience((String) value);
break;
case "exp":
ensureType(key, value, Long.class);
builder.setExpiration(Date.from(Instant.ofEpochSecond(Long.parseLong(value.toString()))));
break;
case "nbf":
ensureType(key, value, Long.class);
builder.setNotBefore(Date.from(Instant.ofEpochSecond(Long.parseLong(value.toString()))));
break;
case "iat":
ensureType(key, value, Long.class);
builder.setIssuedAt(Date.from(Instant.ofEpochSecond(Long.parseLong(value.toString()))));
break;
case "jti":
ensureType(key, value, String.class);
builder.setId((String) value);
break;
default:
builder.claim(key, value);
}
});
builder.signWith(SignatureAlgorithm.HS256, secretService.getHS256SecretBytes());
return new JwtResponse(builder.compact());
}
private void ensureType(String registeredClaim, Object value, Class expectedType) {
boolean isCorrectType =
expectedType.isInstance(value) ||
expectedType == Long.class && value instanceof Integer;
if (!isCorrectType) {
String msg = "Expected type: " + expectedType.getCanonicalName() + " for registered claim: '" +
registeredClaim + "', but got value: " + value + " of type: " + value.getClass().getCanonicalName();
throw new JwtException(msg);
}
}
}

View File

@ -0,0 +1,29 @@
package io.jsonwebtoken.jjwtfun.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@Controller
public class FormController {
@RequestMapping(value = "/jwt-csrf-form", method = GET)
public String csrfFormGet() {
return "jwt-csrf-form";
}
@RequestMapping(value = "/jwt-csrf-form", method = POST)
public String csrfFormPost(@RequestParam(name = "_csrf") String csrf, Model model) {
model.addAttribute("csrf", csrf);
return "jwt-csrf-form-result";
}
@RequestMapping("/expired-jwt")
public String expiredJwt() {
return "expired-jwt";
}
}

View File

@ -0,0 +1,32 @@
package io.jsonwebtoken.jjwtfun.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class HomeController {
@RequestMapping("/")
public String home(HttpServletRequest req) {
String requestUrl = getUrl(req);
return "Available commands (assumes httpie - https://github.com/jkbrzt/httpie):\n\n" +
" http " + requestUrl + "/\n\tThis usage message\n\n" +
" http " + requestUrl + "/static-builder\n\tbuild JWT from hardcoded claims\n\n" +
" http POST " + requestUrl + "/dynamic-builder-general claim-1=value-1 ... [claim-n=value-n]\n\tbuild JWT from passed in claims (using general claims map)\n\n" +
" http POST " + requestUrl + "/dynamic-builder-specific claim-1=value-1 ... [claim-n=value-n]\n\tbuild JWT from passed in claims (using specific claims methods)\n\n" +
" http POST " + requestUrl + "/dynamic-builder-compress claim-1=value-1 ... [claim-n=value-n]\n\tbuild DEFLATE compressed JWT from passed in claims\n\n" +
" http " + requestUrl + "/parser?jwt=<jwt>\n\tParse passed in JWT\n\n" +
" http " + requestUrl + "/parser-enforce?jwt=<jwt>\n\tParse passed in JWT enforcing the 'iss' registered claim and the 'hasMotorcycle' custom claim\n\n" +
" http " + requestUrl + "/get-secrets\n\tShow the signing keys currently in use.\n\n" +
" http " + requestUrl + "/refresh-secrets\n\tGenerate new signing keys and show them.\n\n" +
" http POST " + requestUrl + "/set-secrets HS256=base64-encoded-value HS384=base64-encoded-value HS512=base64-encoded-value\n\tExplicitly set secrets to use in the application.";
}
private String getUrl(HttpServletRequest req) {
return req.getScheme() + "://" +
req.getServerName() +
((req.getServerPort() == 80 || req.getServerPort() == 443) ? "" : ":" + req.getServerPort());
}
}

View File

@ -0,0 +1,35 @@
package io.jsonwebtoken.jjwtfun.controller;
import io.jsonwebtoken.jjwtfun.service.SecretService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController
public class SecretsController extends BaseController {
@Autowired
SecretService secretService;
@RequestMapping(value = "/get-secrets", method = GET)
public Map<String, String> getSecrets() {
return secretService.getSecrets();
}
@RequestMapping(value = "/refresh-secrets", method = GET)
public Map<String, String> refreshSecrets() {
return secretService.refreshSecrets();
}
@RequestMapping(value = "/set-secrets", method = POST)
public Map<String, String> setSecrets(@RequestBody Map<String, String> secrets) {
secretService.setSecrets(secrets);
return secretService.getSecrets();
}
}

View File

@ -0,0 +1,64 @@
package io.jsonwebtoken.jjwtfun.controller;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.jjwtfun.model.JwtResponse;
import io.jsonwebtoken.jjwtfun.service.SecretService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.util.Date;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
@RestController
public class StaticJWTController extends BaseController {
@Autowired
SecretService secretService;
@RequestMapping(value = "/static-builder", method = GET)
public JwtResponse fixedBuilder() throws UnsupportedEncodingException {
String jws = Jwts.builder()
.setIssuer("Stormpath")
.setSubject("msilverman")
.claim("name", "Micah Silverman")
.claim("scope", "admins")
.setIssuedAt(Date.from(Instant.ofEpochSecond(1466796822L))) // Fri Jun 24 2016 15:33:42 GMT-0400 (EDT)
.setExpiration(Date.from(Instant.ofEpochSecond(4622470422L))) // Sat Jun 24 2116 15:33:42 GMT-0400 (EDT)
.signWith(
SignatureAlgorithm.HS256,
secretService.getHS256SecretBytes()
)
.compact();
return new JwtResponse(jws);
}
@RequestMapping(value = "/parser", method = GET)
public JwtResponse parser(@RequestParam String jwt) throws UnsupportedEncodingException {
Jws<Claims> jws = Jwts.parser()
.setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(jwt);
return new JwtResponse(jws);
}
@RequestMapping(value = "/parser-enforce", method = GET)
public JwtResponse parserEnforce(@RequestParam String jwt) throws UnsupportedEncodingException {
Jws<Claims> jws = Jwts.parser()
.requireIssuer("Stormpath")
.require("hasMotorcycle", true)
.setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(jwt);
return new JwtResponse(jws);
}
}

View File

@ -0,0 +1,70 @@
package io.jsonwebtoken.jjwtfun.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JwtResponse {
private String message;
private Status status;
private String exceptionType;
private String jwt;
private Jws<Claims> jws;
public enum Status {
SUCCESS, ERROR
}
public JwtResponse() {}
public JwtResponse(String jwt) {
this.jwt = jwt;
this.status = Status.SUCCESS;
}
public JwtResponse(Jws<Claims> jws) {
this.jws = jws;
this.status = Status.SUCCESS;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getExceptionType() {
return exceptionType;
}
public void setExceptionType(String exceptionType) {
this.exceptionType = exceptionType;
}
public String getJwt() {
return jwt;
}
public void setJwt(String jwt) {
this.jwt = jwt;
}
public Jws<Claims> getJws() {
return jws;
}
public void setJws(Jws<Claims> jws) {
this.jws = jws;
}
}

View File

@ -0,0 +1,74 @@
package io.jsonwebtoken.jjwtfun.service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.crypto.MacProvider;
import io.jsonwebtoken.lang.Assert;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import java.util.HashMap;
import java.util.Map;
@Service
public class SecretService {
private Map<String, String> secrets = new HashMap<>();
private SigningKeyResolver signingKeyResolver = new SigningKeyResolverAdapter() {
@Override
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
return TextCodec.BASE64.decode(secrets.get(header.getAlgorithm()));
}
};
@PostConstruct
public void setup() {
refreshSecrets();
}
public SigningKeyResolver getSigningKeyResolver() {
return signingKeyResolver;
}
public Map<String, String> getSecrets() {
return secrets;
}
public void setSecrets(Map<String, String> secrets) {
Assert.notNull(secrets);
Assert.hasText(secrets.get(SignatureAlgorithm.HS256.getValue()));
Assert.hasText(secrets.get(SignatureAlgorithm.HS384.getValue()));
Assert.hasText(secrets.get(SignatureAlgorithm.HS512.getValue()));
this.secrets = secrets;
}
public byte[] getHS256SecretBytes() {
return TextCodec.BASE64.decode(secrets.get(SignatureAlgorithm.HS256.getValue()));
}
public byte[] getHS384SecretBytes() {
return TextCodec.BASE64.decode(secrets.get(SignatureAlgorithm.HS384.getValue()));
}
public byte[] getHS512SecretBytes() {
return TextCodec.BASE64.decode(secrets.get(SignatureAlgorithm.HS384.getValue()));
}
public Map<String, String> refreshSecrets() {
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256);
secrets.put(SignatureAlgorithm.HS256.getValue(), TextCodec.BASE64.encode(key.getEncoded()));
key = MacProvider.generateKey(SignatureAlgorithm.HS384);
secrets.put(SignatureAlgorithm.HS384.getValue(), TextCodec.BASE64.encode(key.getEncoded()));
key = MacProvider.generateKey(SignatureAlgorithm.HS512);
secrets.put(SignatureAlgorithm.HS512.getValue(), TextCodec.BASE64.encode(key.getEncoded()));
return secrets;
}
}

View File

@ -0,0 +1,17 @@
<html xmlns:th="http://www.thymeleaf.org">
<head>
<!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<h1>JWT CSRF Token expired</h1>
<h3 th:text="${exception.message}"></h3>
<a href="/jwt-csrf-form" class="btn btn-primary">Back</a>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head">
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="viewport" content="width=device-width"/>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,300,400italic,400,600italic,600,700italic,700,800italic,800" rel="stylesheet" type="text/css"/>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<style>
body {
margin-top: 60px;
}
.box {
padding: 50px;
text-align: center;
vertical-align: middle;
}
</style>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
</head>
<body>
<p>Nothing to see here, move along.</p>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<h1>You made it!</h1>
<div style="overflow: scroll">
<h4 th:text="${csrf}">BLARG</h4>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<p/>
<form method="post" th:action="@{/jwt-csrf-form}">
<input type="submit" class="btn btn-primary" value="Click Me!"/>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
package io.jsonwebtoken.jjwtfun;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = JJWTFunApplication.class)
@WebAppConfiguration
public class DemoApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@ -0,0 +1,16 @@
package org.baeldung.mocks.jmockit;
import java.util.List;
public interface ExpectationsCollaborator {
void methodForAny(String s, int i, Boolean b, List<String> l);
void methodForWith(String s, int i, Boolean b, List<String> l);
void methodForNulls(String s, List<String> l, List<Integer> m);
void methodForTimes1();
void methodForTimes2();
void methodForTimes3();
void methodForArgThat(Object o);
String methodReturnsString();
int methodReturnsInt();
Object methodForDelegate(int i);
}

View File

@ -1,7 +1,7 @@
package org.baeldung.mocks.jmockit; package org.baeldung.mocks.jmockit;
public class Model { public class Model {
public String getInfo(){ public String getInfo(){
return "info"; return "info";
} }
} }

View File

@ -0,0 +1,157 @@
package org.baeldung.mocks.jmockit;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Test;
import org.junit.runner.RunWith;
import mockit.Delegate;
import mockit.Expectations;
import mockit.Mocked;
import mockit.StrictExpectations;
import mockit.integration.junit4.JMockit;
@RunWith(JMockit.class)
@SuppressWarnings("unchecked")
public class ExpectationsTest {
@Test
public void testForAny(@Mocked ExpectationsCollaborator mock) throws Exception {
new Expectations() {
{
mock.methodForAny(anyString, anyInt, anyBoolean, (List<String>) any);
}
};
mock.methodForAny("barfooxyz", 0, Boolean.FALSE, new ArrayList<>());
}
@Test
public void testForWith(@Mocked ExpectationsCollaborator mock) throws Exception {
new Expectations() {
{
mock.methodForWith(withSubstring("foo"), withNotEqual(1), withNotNull(), withInstanceOf(List.class));
}
};
mock.methodForWith("barfooxyz", 2, Boolean.TRUE, new ArrayList<>());
}
@Test
public void testWithNulls(@Mocked ExpectationsCollaborator mock) {
// more config
new Expectations() {
{
mock.methodForNulls(anyString, null, (List<Integer>) withNull());
}
};
mock.methodForNulls("blablabla", new ArrayList<String>(), null);
}
@Test
public void testWithTimes(@Mocked ExpectationsCollaborator mock) {
// more config
new Expectations() {
{
// exactly 2 invocations to foo() are expected
mock.methodForTimes1();
times = 2;
// we expect from 1 to 3 invocations to bar()
mock.methodForTimes2();
minTimes = 1;
maxTimes = 3;
mock.methodForTimes3(); // "minTimes = 1" is implied
}
};
mock.methodForTimes1();
mock.methodForTimes1();
mock.methodForTimes2();
mock.methodForTimes2();
mock.methodForTimes2();
mock.methodForTimes3();
}
@Test
public void testCustomArgumentMatching(@Mocked ExpectationsCollaborator mock) {
new Expectations() {
{
mock.methodForArgThat(withArgThat(new BaseMatcher<Object>() {
@Override
public boolean matches(Object item) {
return item instanceof Model && "info".equals(((Model) item).getInfo());
}
@Override
public void describeTo(Description description) {
// NOOP
}
}));
}
};
mock.methodForArgThat(new Model());
}
@Test
public void testResultAndReturns(@Mocked ExpectationsCollaborator mock) {
new StrictExpectations() {
{
// return "foo", an exception and lastly "bar"
mock.methodReturnsString();
result = "foo";
result = new Exception();
result = "bar";
// return 1, 2, 3
mock.methodReturnsInt();
result = new int[] { 1, 2, 3 };
// return "foo" and "bar"
mock.methodReturnsString();
returns("foo", "bar");
// return only 1
mock.methodReturnsInt();
result = 1;
}
};
assertEquals("Should return foo", "foo", mock.methodReturnsString());
try {
mock.methodReturnsString();
} catch (Exception e) {
// NOOP
}
assertEquals("Should return bar", "bar", mock.methodReturnsString());
assertEquals("Should return 1", 1, mock.methodReturnsInt());
assertEquals("Should return 2", 2, mock.methodReturnsInt());
assertEquals("Should return 3", 3, mock.methodReturnsInt());
assertEquals("Should return foo", "foo", mock.methodReturnsString());
assertEquals("Should return bar", "bar", mock.methodReturnsString());
assertEquals("Should return 1", 1, mock.methodReturnsInt());
}
@Test
public void testDelegate(@Mocked ExpectationsCollaborator mock) {
new StrictExpectations() {
{
// return "foo", an exception and lastly "bar"
mock.methodForDelegate(anyInt);
times = 2;
result = new Delegate() {
public int delegate(int i) throws Exception {
if (i < 3) {
return 5;
} else {
throw new Exception();
}
}
};
}
};
assertEquals("Should return 5", 5, mock.methodForDelegate(1));
try {
mock.methodForDelegate(3);
} catch (Exception e) {
// NOOP
}
}
}

View File

@ -26,6 +26,7 @@
<module>httpclient</module> <module>httpclient</module>
<module>jackson</module> <module>jackson</module>
<module>javaxval</module> <module>javaxval</module>
<module>jjwt</module>
<module>jooq-spring</module> <module>jooq-spring</module>
<module>json-path</module> <module>json-path</module>
<module>mockito</module> <module>mockito</module>
@ -82,6 +83,7 @@
<module>redis</module> <module>redis</module>
<module>mutation-testing</module> <module>mutation-testing</module>
<module>spring-mvc-velocity</module>
</modules> </modules>
</project> </project>

170
spring-mvc-velocity/pom.xml Normal file
View File

@ -0,0 +1,170 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung</groupId>
<version>0.1-SNAPSHOT</version>
<artifactId>spring-mvc-velocity</artifactId>
<name>spring-mvc-velocity</name>
<packaging>war</packaging>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- web -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>2.0</version>
</dependency>
<!-- test scoped -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${org.hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${org.hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>spring-mvc-velocity</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<excludes>
<!-- <exclude>**/*ProductionTest.java</exclude> -->
</excludes>
<systemPropertyVariables>
<!-- <provPersistenceTarget>h2</provPersistenceTarget> -->
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<!-- Spring -->
<org.springframework.version>4.1.4.RELEASE</org.springframework.version>
<!-- testing -->
<org.hamcrest.version>1.3</org.hamcrest.version>
<junit.version>4.12</junit.version>
<mockito.version>1.10.19</mockito.version>
<powermock.version>1.6.4</powermock.version>
<httpcore.version>4.4.1</httpcore.version>
<httpclient.version>4.5</httpclient.version>
<rest-assured.version>2.4.1</rest-assured.version>
<!-- Maven plugins -->
<maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version>
<maven-war-plugin.version>2.6</maven-war-plugin.version>
<maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version>
<maven-resources-plugin.version>2.7</maven-resources-plugin.version>
<cargo-maven2-plugin.version>1.4.18</cargo-maven2-plugin.version>
</properties>
</project>

View File

@ -0,0 +1,33 @@
package com.baeldung.mvc.velocity.controller;
import com.baeldung.mvc.velocity.domain.Tutorial;
import com.baeldung.mvc.velocity.service.TutorialsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
@Controller
public class MainController {
private final TutorialsService tutService;
@Autowired
public MainController(TutorialsService tutService) {
this.tutService = tutService;
}
@RequestMapping(method = RequestMethod.GET)
public String listTutorialsPage(Model model) {
List<Tutorial> list = tutService.listTutorials();
model.addAttribute("tutorials", list);
return "index";
}
public TutorialsService getTutService() {
return tutService;
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.mvc.velocity.domain;
public class Tutorial {
private final Integer tutId;
private final String title;
private final String description;
private final String author;
public Tutorial(Integer tutId, String title, String description, String author) {
this.tutId = tutId;
this.title = title;
this.description = description;
this.author = author;
}
}

View File

@ -0,0 +1,18 @@
package com.baeldung.mvc.velocity.service;
import com.baeldung.mvc.velocity.domain.Tutorial;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class TutorialsService {
public List<Tutorial> listTutorials() {
return Arrays.asList(
new Tutorial(1, "Guava", "Introduction to Guava", "GuavaAuthor"),
new Tutorial(2, "Android", "Introduction to Android", "AndroidAuthor")
);
}
}

View File

@ -0,0 +1,4 @@
<div
style="background: #63B175; text-align: center; padding: 5px; margin-top: 10px;">
@Copyright baeldung.com
</div>

View File

@ -0,0 +1,5 @@
<div style="background: #63B175; height: 80px; padding: 5px;">
<div style="float: left">
<h1>Our tutorials</h1>
</div>
</div>

View File

@ -0,0 +1,22 @@
<html>
<head>
<title>Spring & Velocity</title>
</head>
<body>
<div>
#parse("/WEB-INF/fragments/header.vm")
</div>
<div>
<!-- View index.vm is inserted here -->
$screen_content
</div>
<div>
#parse("/WEB-INF/fragments/footer.vm")
</div>
</body>
</html>

View File

@ -0,0 +1,38 @@
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
<!-- Register the annotated components in the container eg : annotated controllers -->
<context:component-scan base-package="com.baeldung.mvc.velocity.*" />
<!-- Tell the container that we are going to use annotations -->
<context:annotation-config />
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath">
<value>/</value>
</property>
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="/WEB-INF/views/" />
<property name="layoutUrl" value="/WEB-INF/layouts/layout.vm" />
<property name="suffix" value=".vm" />
</bean>
</beans>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Empty -->
</beans>

View File

@ -0,0 +1,19 @@
<h1>Index</h1>
<h2>Tutorials list</h2>
<table border="1">
<tr>
<th>Tutorial Id</th>
<th>Tutorial Title</th>
<th>Tutorial Description</th>
<th>Tutorial Author</th>
</tr>
#foreach($tut in $tutorials)
<tr>
<td>$tut.tutId</td>
<td>$tut.title</td>
<td>$tut.description</td>
<td>$tut.author</td>
</tr>
#end
</table>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Spring MVC Velocity</display-name>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

View File

@ -0,0 +1,44 @@
package com.baeldung.mvc.velocity.test;
import com.baeldung.mvc.velocity.controller.MainController;
import com.baeldung.mvc.velocity.domain.Tutorial;
import com.baeldung.mvc.velocity.service.TutorialsService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(MockitoJUnitRunner.class)
public class NavigationControllerTest {
private final MainController mainController = new MainController(Mockito.mock(TutorialsService.class));
private final Model model = new ExtendedModelMap();
@Test
public final void shouldGoToTutorialListView() {
Mockito.when(mainController.getTutService().listTutorials())
.thenReturn(createTutorialList());
final String view = mainController.listTutorialsPage(model);
final List<Tutorial> tutorialListAttribute = (List<Tutorial>) model.asMap().get("tutorials");
assertEquals("index", view);
assertNotNull(tutorialListAttribute);
}
private static List<Tutorial> createTutorialList() {
return Arrays.asList(new Tutorial(1, "TestAuthor", "Test Title", "Test Description"));
}
}

View File

@ -27,12 +27,6 @@
<version>2.0.6</version> <version>2.0.6</version>
</dependency> </dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.9.1</version>
</dependency>
<!-- utils --> <!-- utils -->
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tutorials>
<XMLTutorials> <tutorial tutId="01" type="XML">
<tutorial tutId="01" type="xml"> <author>Jaxb author</author>
<title>XML with Dom4J</title> <date>04/02/2015</date>
<description>XML handling with Dom4J</description> <description>XML Binding with Jaxb</description>
<date>14/06/2016</date> <title>XML with Jaxb</title>
<author>Dom4J tech writer</author> </tutorial>
</tutorial> </tutorials>
</XMLTutorials>