diff --git a/jackson/.classpath b/jackson/.classpath index 8ebf6d9c31..5efa587d72 100644 --- a/jackson/.classpath +++ b/jackson/.classpath @@ -29,7 +29,7 @@ - + diff --git a/jackson/pom.xml b/jackson/pom.xml index c7cd172757..17b0ac507e 100644 --- a/jackson/pom.xml +++ b/jackson/pom.xml @@ -109,6 +109,31 @@ ${mockito.version} test + + + + + org.slf4j + slf4j-api + ${org.slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + + org.slf4j + jcl-over-slf4j + ${org.slf4j.version} + + + + org.slf4j + log4j-over-slf4j + ${org.slf4j.version} + diff --git a/jackson/src/test/java/com/baeldung/jackson/objectmapper/CustomCarDeserializer.java b/jackson/src/test/java/com/baeldung/jackson/objectmapper/CustomCarDeserializer.java new file mode 100644 index 0000000000..88ee1cd673 --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/objectmapper/CustomCarDeserializer.java @@ -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 { + 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; + } +} diff --git a/jackson/src/test/java/com/baeldung/jackson/objectmapper/CustomCarSerializer.java b/jackson/src/test/java/com/baeldung/jackson/objectmapper/CustomCarSerializer.java new file mode 100644 index 0000000000..08c7184d29 --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/objectmapper/CustomCarSerializer.java @@ -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 +{ + 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(); + } +} diff --git a/jackson/src/test/java/com/baeldung/jackson/objectmapper/TestJavaReadWriteJsonExample.java b/jackson/src/test/java/com/baeldung/jackson/objectmapper/TestJavaReadWriteJsonExample.java new file mode 100644 index 0000000000..54ddf469c8 --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/objectmapper/TestJavaReadWriteJsonExample.java @@ -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 listCar = objectMapper.readValue(LOCAL_JSON, new TypeReference>() { + + }); + 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 map = objectMapper.readValue(EXAMPLE_JSON, new TypeReference>() { + }); + assertNotNull(map); + for (final String key : map.keySet()) { + assertNotNull(key); + } + } +} diff --git a/jackson/src/test/java/com/baeldung/jackson/objectmapper/TestSerializationDeserializationFeature.java b/jackson/src/test/java/com/baeldung/jackson/objectmapper/TestSerializationDeserializationFeature.java new file mode 100644 index 0000000000..96918f4c28 --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/objectmapper/TestSerializationDeserializationFeature.java @@ -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")); + } + } +} diff --git a/jackson/src/test/java/com/baeldung/jackson/objectmapper/dto/Car.java b/jackson/src/test/java/com/baeldung/jackson/objectmapper/dto/Car.java new file mode 100644 index 0000000000..bf4309439b --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/objectmapper/dto/Car.java @@ -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; + } +} diff --git a/jackson/src/test/java/com/baeldung/jackson/objectmapper/dto/Request.java b/jackson/src/test/java/com/baeldung/jackson/objectmapper/dto/Request.java new file mode 100644 index 0000000000..2a4cce0fdd --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/objectmapper/dto/Request.java @@ -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; + } +} \ No newline at end of file diff --git a/jjwt/.gitignore b/jjwt/.gitignore new file mode 100644 index 0000000000..f83e8cf07c --- /dev/null +++ b/jjwt/.gitignore @@ -0,0 +1,3 @@ +.idea +target +*.iml diff --git a/jjwt/README.md b/jjwt/README.md new file mode 100644 index 0000000000..47b51038a8 --- /dev/null +++ b/jjwt/README.md @@ -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= + Parse passed in JWT + + http http://localhost:8080/parser-enforce?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/) \ No newline at end of file diff --git a/jjwt/pom.xml b/jjwt/pom.xml new file mode 100644 index 0000000000..24f1c5c5ab --- /dev/null +++ b/jjwt/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + io.jsonwebtoken + jjwtfun + 0.0.1-SNAPSHOT + jar + + jjwtfun + Exercising the JJWT + + + org.springframework.boot + spring-boot-starter-parent + 1.3.5.RELEASE + + + + + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-devtools + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.jsonwebtoken + jjwt + 0.6.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/JJWTFunApplication.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/JJWTFunApplication.java new file mode 100644 index 0000000000..5c106aac70 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/JJWTFunApplication.java @@ -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); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java new file mode 100644 index 0000000000..7d88835243 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java @@ -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()); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java new file mode 100644 index 0000000000..bf88b8aff1 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java @@ -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); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java new file mode 100644 index 0000000000..94e2c6ddc5 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java @@ -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); + } + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/BaseController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/BaseController.java new file mode 100644 index 0000000000..e1e195c6ab --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/BaseController.java @@ -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; + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java new file mode 100644 index 0000000000..c03c63dd80 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java @@ -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 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 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 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); + } + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/FormController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/FormController.java new file mode 100644 index 0000000000..54123c63cb --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/FormController.java @@ -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"; + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/HomeController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/HomeController.java new file mode 100644 index 0000000000..57cd14385e --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/HomeController.java @@ -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=\n\tParse passed in JWT\n\n" + + " http " + requestUrl + "/parser-enforce?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()); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java new file mode 100644 index 0000000000..1ca0973c33 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java @@ -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 getSecrets() { + return secretService.getSecrets(); + } + + @RequestMapping(value = "/refresh-secrets", method = GET) + public Map refreshSecrets() { + return secretService.refreshSecrets(); + } + + @RequestMapping(value = "/set-secrets", method = POST) + public Map setSecrets(@RequestBody Map secrets) { + secretService.setSecrets(secrets); + return secretService.getSecrets(); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java new file mode 100644 index 0000000000..83f5336978 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java @@ -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 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 jws = Jwts.parser() + .requireIssuer("Stormpath") + .require("hasMotorcycle", true) + .setSigningKeyResolver(secretService.getSigningKeyResolver()) + .parseClaimsJws(jwt); + + return new JwtResponse(jws); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java new file mode 100644 index 0000000000..491f003289 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java @@ -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 jws; + + public enum Status { + SUCCESS, ERROR + } + + public JwtResponse() {} + + public JwtResponse(String jwt) { + this.jwt = jwt; + this.status = Status.SUCCESS; + } + + public JwtResponse(Jws 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 getJws() { + return jws; + } + + public void setJws(Jws jws) { + this.jws = jws; + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java new file mode 100644 index 0000000000..4311afa592 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java @@ -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 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 getSecrets() { + return secrets; + } + + public void setSecrets(Map 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 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; + } +} diff --git a/jjwt/src/main/resources/application.properties b/jjwt/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/jjwt/src/main/resources/templates/expired-jwt.html b/jjwt/src/main/resources/templates/expired-jwt.html new file mode 100644 index 0000000000..7bf9ff258e --- /dev/null +++ b/jjwt/src/main/resources/templates/expired-jwt.html @@ -0,0 +1,17 @@ + + + + + +
+
+
+

JWT CSRF Token expired

+

+ + Back +
+
+
+ + \ No newline at end of file diff --git a/jjwt/src/main/resources/templates/fragments/head.html b/jjwt/src/main/resources/templates/fragments/head.html new file mode 100644 index 0000000000..2d5f54e5a0 --- /dev/null +++ b/jjwt/src/main/resources/templates/fragments/head.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + +

Nothing to see here, move along.

+ + \ No newline at end of file diff --git a/jjwt/src/main/resources/templates/jwt-csrf-form-result.html b/jjwt/src/main/resources/templates/jwt-csrf-form-result.html new file mode 100644 index 0000000000..cfb832bf7c --- /dev/null +++ b/jjwt/src/main/resources/templates/jwt-csrf-form-result.html @@ -0,0 +1,18 @@ + + + + + + +
+
+
+

You made it!

+
+

BLARG

+
+
+
+
+ + \ No newline at end of file diff --git a/jjwt/src/main/resources/templates/jwt-csrf-form.html b/jjwt/src/main/resources/templates/jwt-csrf-form.html new file mode 100644 index 0000000000..235f6063c9 --- /dev/null +++ b/jjwt/src/main/resources/templates/jwt-csrf-form.html @@ -0,0 +1,18 @@ + + + + + + +
+
+
+

+

+ +
+
+
+
+ + \ No newline at end of file diff --git a/jjwt/src/test/java/io/jsonwebtoken/jjwtfun/DemoApplicationTests.java b/jjwt/src/test/java/io/jsonwebtoken/jjwtfun/DemoApplicationTests.java new file mode 100644 index 0000000000..82138ea23e --- /dev/null +++ b/jjwt/src/test/java/io/jsonwebtoken/jjwtfun/DemoApplicationTests.java @@ -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() { + } + +} diff --git a/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/ExpectationsCollaborator.java b/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/ExpectationsCollaborator.java new file mode 100644 index 0000000000..510ce222c0 --- /dev/null +++ b/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/ExpectationsCollaborator.java @@ -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 l); + void methodForWith(String s, int i, Boolean b, List l); + void methodForNulls(String s, List l, List m); + void methodForTimes1(); + void methodForTimes2(); + void methodForTimes3(); + void methodForArgThat(Object o); + String methodReturnsString(); + int methodReturnsInt(); + Object methodForDelegate(int i); +} diff --git a/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/Model.java b/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/Model.java index 54249dcd1d..c3b63d11b4 100644 --- a/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/Model.java +++ b/mocks/jmockit/src/main/java/org/baeldung/mocks/jmockit/Model.java @@ -1,7 +1,7 @@ package org.baeldung.mocks.jmockit; public class Model { - public String getInfo(){ - return "info"; - } + public String getInfo(){ + return "info"; + } } diff --git a/mocks/jmockit/src/test/java/org/baeldung/mocks/jmockit/ExpectationsTest.java b/mocks/jmockit/src/test/java/org/baeldung/mocks/jmockit/ExpectationsTest.java new file mode 100644 index 0000000000..5d0af157d1 --- /dev/null +++ b/mocks/jmockit/src/test/java/org/baeldung/mocks/jmockit/ExpectationsTest.java @@ -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) 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) withNull()); + } + }; + mock.methodForNulls("blablabla", new ArrayList(), 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() { + @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 + } + } +} diff --git a/pom.xml b/pom.xml index 1966d1485a..82cf85208c 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ httpclient jackson javaxval + jjwt jooq-spring json-path mockito @@ -82,6 +83,7 @@ redis mutation-testing + spring-mvc-velocity diff --git a/spring-mvc-velocity/pom.xml b/spring-mvc-velocity/pom.xml new file mode 100644 index 0000000000..c655745b05 --- /dev/null +++ b/spring-mvc-velocity/pom.xml @@ -0,0 +1,170 @@ + + 4.0.0 + com.baeldung + 0.1-SNAPSHOT + spring-mvc-velocity + + spring-mvc-velocity + war + + + + + + + org.springframework + spring-web + ${org.springframework.version} + + + org.springframework + spring-webmvc + ${org.springframework.version} + + + org.springframework + spring-core + ${org.springframework.version} + + + org.springframework + spring-context-support + ${org.springframework.version} + + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + org.apache.velocity + velocity + 1.7 + + + + org.apache.velocity + velocity-tools + 2.0 + + + + + + junit + junit + ${junit.version} + test + + + + org.hamcrest + hamcrest-core + ${org.hamcrest.version} + test + + + org.hamcrest + hamcrest-library + ${org.hamcrest.version} + test + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito + ${powermock.version} + test + + + + + + spring-mvc-velocity + + + src/main/resources + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + + + + + + + + + + + + + 4.1.4.RELEASE + + + + 1.3 + 4.12 + 1.10.19 + 1.6.4 + + 4.4.1 + 4.5 + + 2.4.1 + + + 3.5.1 + 2.6 + 2.19.1 + 2.7 + 1.4.18 + + + + \ No newline at end of file diff --git a/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/controller/MainController.java b/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/controller/MainController.java new file mode 100644 index 0000000000..78a6e06e7a --- /dev/null +++ b/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/controller/MainController.java @@ -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 list = tutService.listTutorials(); + model.addAttribute("tutorials", list); + return "index"; + } + + public TutorialsService getTutService() { + return tutService; + } +} \ No newline at end of file diff --git a/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/domain/Tutorial.java b/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/domain/Tutorial.java new file mode 100644 index 0000000000..bc0118790a --- /dev/null +++ b/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/domain/Tutorial.java @@ -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; + } +} diff --git a/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/service/TutorialsService.java b/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/service/TutorialsService.java new file mode 100644 index 0000000000..4b6c5de427 --- /dev/null +++ b/spring-mvc-velocity/src/main/java/com/baeldung/mvc/velocity/service/TutorialsService.java @@ -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 listTutorials() { + return Arrays.asList( + new Tutorial(1, "Guava", "Introduction to Guava", "GuavaAuthor"), + new Tutorial(2, "Android", "Introduction to Android", "AndroidAuthor") + ); + } +} diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/fragments/footer.vm b/spring-mvc-velocity/src/main/webapp/WEB-INF/fragments/footer.vm new file mode 100644 index 0000000000..41bb36ce5e --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/fragments/footer.vm @@ -0,0 +1,4 @@ +
+ @Copyright baeldung.com +
\ No newline at end of file diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/fragments/header.vm b/spring-mvc-velocity/src/main/webapp/WEB-INF/fragments/header.vm new file mode 100644 index 0000000000..8fffa6cdab --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/fragments/header.vm @@ -0,0 +1,5 @@ +
+
+

Our tutorials

+
+
\ No newline at end of file diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/layouts/layout.vm b/spring-mvc-velocity/src/main/webapp/WEB-INF/layouts/layout.vm new file mode 100644 index 0000000000..203e675cfb --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/layouts/layout.vm @@ -0,0 +1,22 @@ + + + Spring & Velocity + + +
+ #parse("/WEB-INF/fragments/header.vm") +
+ + +
+ + + $screen_content + +
+ +
+ #parse("/WEB-INF/fragments/footer.vm") +
+ + \ No newline at end of file diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/mvc-servlet.xml b/spring-mvc-velocity/src/main/webapp/WEB-INF/mvc-servlet.xml new file mode 100644 index 0000000000..8b4fd570fe --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/mvc-servlet.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + / + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/spring-context.xml b/spring-mvc-velocity/src/main/webapp/WEB-INF/spring-context.xml new file mode 100644 index 0000000000..2f3b0f19bb --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/spring-context.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/views/index.vm b/spring-mvc-velocity/src/main/webapp/WEB-INF/views/index.vm new file mode 100644 index 0000000000..d1ae0b02cb --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/views/index.vm @@ -0,0 +1,19 @@ +

Index

+ +

Tutorials list

+ + + + + + + +#foreach($tut in $tutorials) + + + + + + +#end +
Tutorial IdTutorial TitleTutorial DescriptionTutorial Author
$tut.tutId$tut.title$tut.description$tut.author
\ No newline at end of file diff --git a/spring-mvc-velocity/src/main/webapp/WEB-INF/web.xml b/spring-mvc-velocity/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..72050c0d37 --- /dev/null +++ b/spring-mvc-velocity/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,32 @@ + + + + Spring MVC Velocity + + + mvc + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/mvc-servlet.xml + + 1 + + + + mvc + /* + + + + contextConfigLocation + /WEB-INF/spring-context.xml + + + + org.springframework.web.context.ContextLoaderListener + + \ No newline at end of file diff --git a/spring-mvc-velocity/src/test/java/com/baeldung/mvc/velocity/test/NavigationControllerTest.java b/spring-mvc-velocity/src/test/java/com/baeldung/mvc/velocity/test/NavigationControllerTest.java new file mode 100644 index 0000000000..332c4d4c33 --- /dev/null +++ b/spring-mvc-velocity/src/test/java/com/baeldung/mvc/velocity/test/NavigationControllerTest.java @@ -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 tutorialListAttribute = (List) model.asMap().get("tutorials"); + + assertEquals("index", view); + assertNotNull(tutorialListAttribute); + } + + + private static List createTutorialList() { + return Arrays.asList(new Tutorial(1, "TestAuthor", "Test Title", "Test Description")); + } +} diff --git a/xml/pom.xml b/xml/pom.xml index 9d88bd75eb..d204eea45f 100644 --- a/xml/pom.xml +++ b/xml/pom.xml @@ -27,12 +27,6 @@ 2.0.6 - - xerces - xercesImpl - 2.9.1 - - commons-io diff --git a/xml/src/test/resources/example_new.xml b/xml/src/test/resources/example_new.xml index 020760fdd3..646d938869 100644 --- a/xml/src/test/resources/example_new.xml +++ b/xml/src/test/resources/example_new.xml @@ -1,10 +1,9 @@ - - - - - XML with Dom4J - XML handling with Dom4J - 14/06/2016 - Dom4J tech writer - - + + + + Jaxb author + 04/02/2015 + XML Binding with Jaxb + XML with Jaxb + +