Commit 072cb30c by Brian Holsen

add springboot test code for jdk8

parent b994936b
# IDE
*.iml
.idea
# Compiled classes
/target
# Application specific
/prime-numbers.log
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# prime-numbers service
## What does is do?
**generates/checks prime numbers** via exposed REST service.
## How to use it?
### Deployed public instance on EC2
* REST API is exposed on ```http://mits4u.co.uk:8081/primes-api/index.html```.
### Build & run locally
* to build an application as far runnable JAR run ```mvn clean package```
* to run an application you can either:
- run it directly from the code base with ```mvn clean spring-boot:run``` command
- run it using the fat JAR with ```java -Xmx[MAXIMUM-size]m -Xms[INITIAL-SIZE]m -jar prime-numbers-[VERSION].jar```
* REST API will be exposed in ```http://localhost:8081/primes-api/index.html```.
### Deploy the application to target environment
* CI/CD would be needed
## Assumptions
* **Caching** is out of scope. Might consider adding caching layer, auto cache warm-up, etc.
* **Limitations** assumption is that prime numbers belong to range 0 <= n <= 2^24
- This range will generate ~1M primes with biggest value of 16777213
- Note: rendering all results (~10MB) on client's browser without specifying bigger ceiling in range will be very intense
* **Very large numbers** very large numbers (i.e. > 2 ^ 24) computation involve challenges further multiplied by simultaneous requests:
- Each request would need immense space/processing, we should consider JVM tuning, results caching and load-balancing as minimum;
- Depending on requirements might consider Grid computing and potentially adjusting algorithm for that;
- Might consider asynchronous-like API with separate submit/result operations to avoid blocking the client;
- Might consider internal large number queueing approach coupled with priority based on number and results caching.
- Might consider different form of response for the client as numbers collection as text grows rapidly for very large numbers
* **Versioning** of API is out of scope. Would be possible incorporating in URL, using version headers, etc.
* **NFRs** (i.e. security, auditing, monitoring, fail-overs (i.e. due to overflows), load/stress testing, etc.) is out of scope.
* **Statistics** collection (i.e. usage, average execution times, common ranges) is out of scope.
## Implementation details
* We might utilize [floor, ceiling] range in each of the strategy itself. That would help saving space when result collection is very big and we will need to trim it adjusting to range anyway. Decided that extra implementation complexity is not worth the trouble.
* Considering big numbers, space complexity is very important here, so chose boolean[] instead of Boolean Vector. Also space for primes result collection would be something to watch out if to increase ceiling limitation.
* Eratosthenes implementation uses Java 8 parallel streams backed with ForkJoinPool and auto-assigned number of threads.
* Sundaram implementation with parallel streams showed very poor performance on > 2^20 numbers. Decided to use custom ExecutorService with CountDownLatch approach to give more flexibility to number of threads used. Extra implementation and testing effort, heavily improved performance though.
\ No newline at end of file
<?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>uk.co.mits4u</groupId>
<artifactId>prime-numbers</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey2-jaxrs</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.19</version>
<executions>
<execution>
<id>integration-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package uk.co.mits4u.primes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
\ No newline at end of file
package uk.co.mits4u.primes.api;
public enum AlgorithmName {
ERATOSTHENES,
SUNDARAM
}
package uk.co.mits4u.primes.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.Collection;
@Api(value = "prime-numbers", description = "Prime numbers service", produces = MediaType.APPLICATION_JSON)
@Path("/")
public interface PrimesApi {
@ApiOperation(value = "Calculate primes in specified [floor, ceiling] range")
@GET
@Path("/primes")
@Produces(MediaType.APPLICATION_JSON)
Collection<Integer> getPrimesInRange(@ApiParam(value = "more or equal [default 0]", required = false)
@QueryParam("floor") int floor,
@ApiParam(value = "less or equal then 2^24 = 16777216", required = true)
@QueryParam("ceiling") int ceiling,
@ApiParam(value = "algorithm", required = false)
@QueryParam("algorithm") @DefaultValue("ERATOSTHENES") AlgorithmName algorithmName);
@ApiOperation(value = "Check if provided number is prime")
@GET
@Path("/{number}/isPrime")
@Produces(MediaType.APPLICATION_JSON)
boolean isPrime(@ApiParam(value = "less or equal then 2^24 = 16777216", required = true)
@PathParam("number") int numberToCheck,
@ApiParam(value = "algorithm", required = false)
@QueryParam("algorithm") @DefaultValue("ERATOSTHENES") AlgorithmName algorithmName);
}
package uk.co.mits4u.primes.config;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
public Response toResponse(IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(e.getMessage())
.type(MediaType.TEXT_PLAIN_TYPE)
.build();
}
}
\ No newline at end of file
package uk.co.mits4u.primes.config;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.wadl.internal.WadlResource;
import org.springframework.stereotype.Component;
import uk.co.mits4u.primes.service.PrimesService;
import javax.ws.rs.ApplicationPath;
@Component
@ApplicationPath("/numbers")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
registerEndpoints();
configureSwagger();
}
private void configureSwagger() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.0");
beanConfig.setTitle("Primes API");
beanConfig.setBasePath("/numbers");
beanConfig.setResourcePackage("uk.co.mits4u.primes");
beanConfig.setPrettyPrint(true);
beanConfig.setScan(true);
}
private void registerEndpoints() {
register(ApiListingResource.class);
register(SwaggerSerializers.class);
register(WadlResource.class);
register(PrimesService.class);
}
}
\ No newline at end of file
package uk.co.mits4u.primes.config;
import org.springframework.beans.factory.config.ServiceLocatorFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import uk.co.mits4u.primes.service.PrimeStrategyFactory;
@Configuration
public class WebMvcConfig {
@Bean
public ServiceLocatorFactoryBean primeStrategyFactory() {
ServiceLocatorFactoryBean locatorFactoryBean = new ServiceLocatorFactoryBean();
locatorFactoryBean.setServiceLocatorInterface(PrimeStrategyFactory.class);
return locatorFactoryBean;
}
}
package uk.co.mits4u.primes.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
@RestController
public class IndexController {
@GetMapping("/")
public RedirectView redirect(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
if (StringUtils.isNoneEmpty(request.getHeader("X-Forwarded-Host"))) {
sb.append(request.getHeader("X-Forwarded-Proto"));
sb.append("://");
sb.append(request.getHeader("X-Forwarded-Host"));
sb.append(":");
sb.append(request.getHeader("X-Forwarded-Port"));
sb.append("/");
} else {
sb.append(request.getRequestURL().toString());
}
sb.append("primes-api/index.html");
return new RedirectView(sb.toString());
}
}
package uk.co.mits4u.primes.service;
import org.springframework.stereotype.Component;
@Component
public class NumberValidator {
public static final int MAX_NUMBER = 16777216;
public void validateRange(int floor, int ceiling) {
validateNumber(floor);
validateNumber(ceiling);
if (floor > ceiling) {
throw new IllegalArgumentException("floor [" + floor + "] cannot be higher then ceiling [" + ceiling + "]");
}
}
public void validateNumber(int number) {
if (number < 0) {
throw new IllegalArgumentException("prime cannot be negative. [" + number + "] is invalid");
}
if (number > MAX_NUMBER) {
throw new IllegalArgumentException("[" + number + "] is invalid. Select number <= 16777216 = 2^24. ");
}
}
}
package uk.co.mits4u.primes.service;
import java.util.Collection;
public interface PrimeStrategy {
Collection<Integer> generatePrimes(int limitingNumber);
boolean isPrime(int numberToCheck);
}
package uk.co.mits4u.primes.service;
public interface PrimeStrategyFactory {
PrimeStrategy getStrategy(String algorithmName);
}
\ No newline at end of file
package uk.co.mits4u.primes.service;
import com.google.common.collect.ImmutableSortedSet;
import org.apache.commons.lang3.Validate;
import org.apache.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import uk.co.mits4u.primes.api.AlgorithmName;
import uk.co.mits4u.primes.api.PrimesApi;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.*;
import static java.lang.String.format;
@Service
public class PrimesService implements PrimesApi {
private static Logger logger = Logger.getLogger(PrimesService.class);
@Resource
private NumberValidator numberValidator;
@Resource
private PrimeStrategyFactory primeStrategyFactory;
@Override
public boolean isPrime(int numberToCheck, AlgorithmName algorithmName) {
String params = format("number=[%s], algorithm=[%s]", numberToCheck, algorithmName);
logger.info("starting isPrime with [" + params + "]");
long start = System.currentTimeMillis();
numberValidator.validateNumber(numberToCheck);
PrimeStrategy primeStrategy = resolveStrategy(algorithmName);
boolean isPrime = primeStrategy.isPrime(numberToCheck);
long timeTaken = System.currentTimeMillis() - start;
logger.info(format("finished isPrime with [%s] in %s ms -> result: [%s]", params, timeTaken, isPrime));
return isPrime;
}
@Override
public Collection<Integer> getPrimesInRange(int floor, int ceiling, AlgorithmName algorithmName) {
String params = format("range=[%s, %s], algorithm=[%s]", floor, ceiling, algorithmName);
logger.info("starting calculation with [" + params + "]");
long start = System.currentTimeMillis();
numberValidator.validateRange(floor, ceiling);
PrimeStrategy primeStrategy = resolveStrategy(algorithmName);
Collection<Integer> allPrimes = primeStrategy.generatePrimes(ceiling);
ImmutableSortedSet<Integer> primesInRange = ImmutableSortedSet.copyOf(allPrimes).subSet(floor, true, ceiling, true);
long timeTaken = System.currentTimeMillis() - start;
logger.info(format("ended calculation with [%s] in %s ms -> found %s primes ", params, timeTaken, primesInRange.size()));
return primesInRange;
}
private PrimeStrategy resolveStrategy(AlgorithmName algorithm) {
PrimeStrategy primeStrategy = primeStrategyFactory.getStrategy(algorithm.name());
Validate.notNull(primeStrategy, "Could note resolve prime strategy for '" + algorithm + "' algorithm");
return primeStrategy;
}
}
package uk.co.mits4u.primes.service.strategy;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import uk.co.mits4u.primes.api.AlgorithmName;
import uk.co.mits4u.primes.api.PrimesApi;
import uk.co.mits4u.primes.service.PrimeStrategy;
import javax.annotation.concurrent.ThreadSafe;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@Component("ERATOSTHENES")
@ThreadSafe
class EratosthenesPrimeStrategy implements PrimeStrategy {
private static int FIRST_PRIME = 2;
@Override
public boolean isPrime(int numberToCheck) {
Collection<Integer> primes = generatePrimes(numberToCheck);
return primes.contains(numberToCheck);
}
@Override
public Collection<Integer> generatePrimes(int maxPrime) {
if (maxPrime == 0) {
return Lists.newLinkedList();
}
boolean[] primeFlags = initAllAsPrime(maxPrime);
markNonPrimes(maxPrime, primeFlags);
Collection<Integer> primes = collectPrimes(primeFlags);
return primes;
}
private boolean[] initAllAsPrime(int limit) {
boolean[] flags = new boolean[limit + 1];
Arrays.fill(flags, true);
return flags;
}
private void markNonPrimes(int limit, boolean[] primeFlags) {
int maxPotentialPrime = (int) Math.sqrt(limit);
Stream.iterate(FIRST_PRIME, i -> ++i).limit(maxPotentialPrime - 1)
.parallel().forEach(potentialPrime -> markMultiplesAsNonPrimes(potentialPrime, primeFlags)
);
}
private void markMultiplesAsNonPrimes(int prime, boolean[] primeFlags) {
for (int i = prime * prime; i < primeFlags.length; i += prime) {
primeFlags[i] = false;
}
}
private Collection<Integer> collectPrimes(boolean[] primeFlags) {
return IntStream.range(FIRST_PRIME, primeFlags.length)
.filter(index -> primeFlags[index])
.mapToObj(prime -> prime)
.collect(Collectors.toList());
}
}
package uk.co.mits4u.primes.service.strategy;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import uk.co.mits4u.primes.service.PrimeStrategy;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@Component("SUNDARAM")
@ThreadSafe
class SundaramPrimeStrategy implements PrimeStrategy {
private static final int FIRST_PRIME = 2;
@Value("${primes.sundaram.thread.count}")
private int threadCount;
private ExecutorService threadPool;
@PostConstruct
protected void construct() {
threadPool = Executors.newFixedThreadPool(threadCount);
}
@PreDestroy
protected void destroy() {
threadPool.shutdown();
}
@Override
public boolean isPrime(int numberToCheck) {
Collection<Integer> primes = generatePrimes(numberToCheck);
return primes.contains(numberToCheck);
}
@Override
public Collection<Integer> generatePrimes(int maxPrime) {
if (maxPrime == 0 || maxPrime == 1) {
return Lists.newLinkedList();
}
int maxPrimeInclusive = maxPrime + 1;
boolean[] primeFlags = initAllAsPrime(maxPrimeInclusive);
markNonPrimes(maxPrimeInclusive, primeFlags);
Collection<Integer> primes = collectPrimes(primeFlags, maxPrimeInclusive);
return primes;
}
private boolean[] initAllAsPrime(int limit) {
boolean[] flags = new boolean[limit];
Arrays.fill(flags, true);
return flags;
}
private void markNonPrimes(int limit, boolean[] primeFlags) {
try {
int n = getSundaramLimit(limit);
Collection<ImmutablePair<Integer, Integer>> ranges = splitToRanges(n, threadCount);
CountDownLatch countDownLatch = new CountDownLatch(ranges.size());
ranges.forEach(range ->
threadPool.submit(() -> {
for (int i = range.getLeft() + 1; i <= range.getRight(); i++) {
for (int j = i; j <= (n - i) / (1 + 2 * i); j++) {
primeFlags[i + j + 2 * i * j] = false;
}
}
countDownLatch.countDown();
})
);
countDownLatch.await();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
private Collection<Integer> collectPrimes(boolean[] primeFlags, int limit) {
final Collection<Integer> primes = Lists.newLinkedList();
primes.add(FIRST_PRIME);
boolean indexForPrime = true;
IntStream.range(1, getSundaramLimit(limit))
.filter(flagIndex -> primeFlags[flagIndex] == indexForPrime)
.forEach(primeFlagIndex -> primes.add(2 * primeFlagIndex + 1));
return primes;
}
private int getSundaramLimit(int limit) {
return limit / 2;
}
private Collection<ImmutablePair<Integer, Integer>> splitToRanges(int n, int slots) {
int rangeSize = n % slots == 0 ? n / slots : (n / slots) + 1;
Collection<ImmutablePair<Integer, Integer>> ranges = Stream.iterate(0, (left) -> left + rangeSize)
.limit(slots)
.map((left) -> new ImmutablePair<>(left, left + rangeSize < n ? left + rangeSize : n))
.filter((p) -> p.left < n)
.collect(Collectors.toList());
return ranges;
}
void setThreadCount(int threadCount) {
this.threadCount = threadCount;
}
}
spring.main.show_banner: false
management.port: 8080
management.context-path: /manage
server.port: 8080
primes.sundaram.thread.count: 3
logging.file: prime-numbers.log
logging.level.uk.co.mits4u.primes: INFO
\ No newline at end of file
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "/numbers/swagger.json",
validatorUrl : null,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
qp = (window.location.hash || location.search).substring(1);
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>
This source diff could not be displayed because it is too large. You can view the blob instead.
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAm/EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAs6TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAogpBA;;;;;AAk1QA;AAm4DA;;;;;;AAo4YA;;;;;;AA8jaA;AAumvBA","sourceRoot":""}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA21CA;;;;;;AAspFA","sourceRoot":""}
\ No newline at end of file
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AA0/cA","sourceRoot":""}
\ No newline at end of file
package uk.co.mits4u.primes.api;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import uk.co.mits4u.primes.Application;
import java.net.URL;
import java.util.Collection;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PrimesApiIT {
@Value("${local.server.port}")
private int port;
private URL base;
private TestRestTemplate template;
@Before
public void setUp() throws Exception {
template = new TestRestTemplate();
}
@Test
public void isPrimeForBiggestAllowed() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/16777213/isPrime");
ResponseEntity<Boolean> response = template.getForEntity(base.toString(), Boolean.class);
Boolean isPrime = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(isPrime).isTrue();
}
@Test
public void isNonPrime() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/10/isPrime");
ResponseEntity<Boolean> response = template.getForEntity(base.toString(), Boolean.class);
Boolean isPrime = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(isPrime).isFalse();
}
@Test
public void isPrimeWithChosenStrategy() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/101/isPrime?algorithm=SUNDARAM");
ResponseEntity<Boolean> response = template.getForEntity(base.toString(), Boolean.class);
Boolean isPrime = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(isPrime).isTrue();
}
@Test
public void calculatePrimesWithDefaults() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/primes?ceiling=10");
ResponseEntity<Collection> response = template.getForEntity(base.toString(), Collection.class);
Collection<Integer> primes = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(primes).containsExactly(2, 3, 5, 7);
}
@Test
public void calculatePrimesWithSpecifiedRangeAndAlgorithm() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/primes?floor=0&ceiling=10&algorithm=SUNDARAM");
ResponseEntity<Collection> response = template.getForEntity(base.toString(), Collection.class);
Collection<Integer> primes = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(primes).containsExactly(2, 3, 5, 7);
}
@Test
public void calculatePrimesWithLetters() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/one/isPrime");
ResponseEntity<String> response = template.getForEntity(base.toString(), String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void calculatePrimesForWrongRange() throws Exception {
base = new URL("http://localhost:" + port + "/numbers/primes?floor=10&ceiling=1");
ResponseEntity<String> response = template.getForEntity(base.toString(), String.class);
String responseBody = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(responseBody).contains("floor [10] cannot be higher then ceiling [1]");
}
}
\ No newline at end of file
package uk.co.mits4u.primes.service;
import javafx.animation.KeyValue;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.springframework.stereotype.Component;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static uk.co.mits4u.primes.service.NumberValidator.MAX_NUMBER;
@Component
public class NumberValidatorTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@InjectMocks
private NumberValidator numberValidator;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testValidateZeroRange() throws Exception {
numberValidator.validateRange(0, 0);
}
@Test
public void testValidateMaxRange() throws Exception {
numberValidator.validateRange(0, MAX_NUMBER);
}
@Test
public void testValidateZeroRangeAtMax() throws Exception {
numberValidator.validateRange(MAX_NUMBER, MAX_NUMBER);
}
@Test
public void testValidateCorrectRange() throws Exception {
Stream.of(ImmutablePair.of(0, 1), ImmutablePair.of(0, 2), ImmutablePair.of(1, 1), ImmutablePair.of(1, 2),
ImmutablePair.of(100, 55000), ImmutablePair.of(1, 100000), ImmutablePair.of(0, 900000), ImmutablePair.of(9000, MAX_NUMBER))
.forEach(range -> numberValidator.validateRange(0, 1));
}
@Test
public void testValidateMaximumPossibleRange() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("prime cannot be negative. [-2147483648] is invalid");
numberValidator.validateRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
}
@Test
public void testValidateMaximumPossiblePositiveRange() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("[2147483647] is invalid. Select number <= 16777216 = 2^24");
numberValidator.validateRange(0, Integer.MAX_VALUE);
}
@Test
public void testValidateFloorBiggerThenCeiling() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("floor [1] cannot be higher then ceiling [0]");
numberValidator.validateRange(1, 0);
}
@Test
public void testMaxInt() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("[2147483647] is invalid. Select number <= 16777216 = 2^24");
numberValidator.validateNumber(Integer.MAX_VALUE);
}
@Test
public void testMinInt() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("prime cannot be negative. [-2147483648] is invalid");
numberValidator.validateNumber(Integer.MIN_VALUE);
}
@Test
public void testMaxNumber() throws Exception {
numberValidator.validateNumber(MAX_NUMBER);
}
@Test
public void testZero() throws Exception {
numberValidator.validateNumber(0);
}
@Test
public void testNegativeNumber() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("prime cannot be negative. [-1] is invalid");
numberValidator.validateNumber(-1);
}
@Test
public void testNumberTooBig() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("[16777217] is invalid. Select number <= 16777216 = 2^24");
numberValidator.validateNumber(MAX_NUMBER + 1);
}
}
\ No newline at end of file
package uk.co.mits4u.primes.service;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.context.annotation.Primary;
import uk.co.mits4u.primes.api.AlgorithmName;
import java.util.Collection;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
public class PrimesServiceTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@InjectMocks
private PrimesService primesService;
@Mock
private NumberValidator numberValidator;
@Mock
private PrimeStrategyFactory primeStrategyFactory;
@Mock
private PrimeStrategy eratosthenesPrimeStrategy;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doNothing().when(numberValidator).validateNumber(anyInt());
when(primeStrategyFactory.getStrategy("ERATOSTHENES")).thenReturn(eratosthenesPrimeStrategy);
when(eratosthenesPrimeStrategy.isPrime(anyInt())).thenReturn(true);
when(eratosthenesPrimeStrategy.generatePrimes(anyInt())).thenReturn(Lists.newArrayList(2, 3, 5, 7));
}
@Test
public void testIsPrime() throws Exception {
boolean isPrime = primesService.isPrime(1, AlgorithmName.ERATOSTHENES);
verify(numberValidator).validateNumber(1);
assertThat(isPrime).isTrue();
}
@Test(expected = IllegalArgumentException.class)
public void testIsPrimeWhenValidatorThrowsException() throws Exception {
doThrow(new IllegalArgumentException()).when(numberValidator).validateNumber(-1);
primesService.isPrime(-1, AlgorithmName.ERATOSTHENES);
}
@Test
public void testGetPrimesInRange() throws Exception {
Collection<Integer> results = primesService.getPrimesInRange(1, 10, AlgorithmName.ERATOSTHENES);
verify(numberValidator).validateRange(1, 10);
assertThat(results).containsExactly(2, 3, 5, 7);
}
@Test(expected = IllegalArgumentException.class)
public void testGetPrimesWithValidationException() throws Exception {
doThrow(new IllegalArgumentException()).when(numberValidator).validateRange(100, 1);
primesService.getPrimesInRange(100, 1, AlgorithmName.ERATOSTHENES);
}
@Test
public void testGetPrimesInRangeWithExactInclusiveRange() throws Exception {
Collection<Integer> results = primesService.getPrimesInRange(2, 7, AlgorithmName.ERATOSTHENES);
verify(numberValidator).validateRange(2, 7);
assertThat(results).containsExactly(2, 3, 5, 7);
}
@Test
public void testGetPrimesInRangeWithTrimmingResults() throws Exception {
Collection<Integer> results = primesService.getPrimesInRange(3, 3, AlgorithmName.ERATOSTHENES);
verify(numberValidator).validateRange(3, 3);
assertThat(results).containsExactly(3);
}
@Test
public void testUnknownStrategy() throws Exception {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("Could note resolve prime strategy for 'SUNDARAM' algorithm");
primesService.getPrimesInRange(1, 10, AlgorithmName.SUNDARAM);
}
}
\ No newline at end of file
package uk.co.mits4u.primes.service.strategy;
import org.junit.Test;
import uk.co.mits4u.primes.service.PrimeStrategy;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
public abstract class AbstractPrimeStrategyTester {
private PrimeStrategy primeStrategy;
private int[] primes;
public AbstractPrimeStrategyTester(PrimeStrategy primeStrategy) {
this.primeStrategy = primeStrategy;
primes = new int[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};
}
@Test
public void testIsPrime() throws Exception {
IntStream.of(primes).forEach((prime) -> {
boolean isPrime = primeStrategy.isPrime(prime);
assertThat(isPrime).as("checking prime " + prime).isTrue();
}
);
}
@Test
public void testIsNotPrime() throws Exception {
int[] nonPrimes = new int[]{0, 1, 4, 6, 8, 9, 10, 12, 14, 16, 18, 20};
IntStream.of(nonPrimes).forEach((nonPrime) -> {
boolean isPrime = primeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking non prime " + nonPrime).isFalse();
}
);
}
@Test
public void testGeneratePrimes() throws Exception {
Collection<Integer> primeResults = primeStrategy.generatePrimes(100);
List<Integer> expectedResults = IntStream.of(primes).mapToObj(i -> i).collect(Collectors.toList());
assertThat(primeResults).containsOnlyElementsOf(expectedResults);
}
@Test
public void testGeneratePrimesZero() throws Exception {
Collection<Integer> expectedResults = primeStrategy.generatePrimes(0);
assertThat(expectedResults).isEmpty();
}
@Test
public void testGeneratePrimesOne() throws Exception {
Collection<Integer> expectedResults = primeStrategy.generatePrimes(1);
assertThat(expectedResults).isEmpty();
}
@Test
public void testGeneratePrimesTwo() throws Exception {
Collection<Integer> expectedResults = primeStrategy.generatePrimes(2);
assertThat(expectedResults).containsExactly(2);
}
@Test
public void testIsPrimeBigNumbers() {
IntStream.of(16777213, 15485867).forEach((nonPrime) -> {
boolean isPrime = primeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking big prime " + nonPrime).isTrue();
}
);
}
@Test
public void testIsNotPrimeBigNumbers() {
IntStream.of(16777216, 15485866).forEach((nonPrime) -> {
boolean isPrime = primeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking big non prime " + nonPrime).isFalse();
}
);
}
@Test
public void testIsPrimeBigNumbersSimultaneously() {
IntStream.of(16777213, 15485867).parallel().forEach((nonPrime) -> {
boolean isPrime = primeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking big prime " + nonPrime).isTrue();
}
);
}
@Test
public void testIsNotPrimeBigNumbersSimultaneously() {
IntStream.of(16777216, 15485866).parallel().forEach((nonPrime) -> {
boolean isPrime = primeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking big non prime " + nonPrime).isFalse();
}
);
}
}
package uk.co.mits4u.primes.service.strategy;
import org.junit.BeforeClass;
public class EratosthenesPrimeStrategyStrategyTest extends AbstractPrimeStrategyTester {
private static EratosthenesPrimeStrategy eratosthenesPrimeStrategy;
@BeforeClass
public static void setUp() {
eratosthenesPrimeStrategy = new EratosthenesPrimeStrategy();
}
public EratosthenesPrimeStrategyStrategyTest() {
super(eratosthenesPrimeStrategy);
}
}
\ No newline at end of file
package uk.co.mits4u.primes.service.strategy;
import org.junit.*;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
public class SundaramPrimeStrategyTest extends AbstractPrimeStrategyTester {
private static SundaramPrimeStrategy sundaramPrimeStrategy;
@BeforeClass
public static void setUpBeforeClass() {
sundaramPrimeStrategy = new SundaramPrimeStrategy();
sundaramPrimeStrategy.setThreadCount(2);
sundaramPrimeStrategy.construct();
}
@AfterClass
public static void tearDown() {
sundaramPrimeStrategy.destroy();
}
public SundaramPrimeStrategyTest() {
super(sundaramPrimeStrategy);
}
@Test
public void testIsPrimeWithDifferentNumberOfThreads() {
IntStream.of(1, 2, 3, 100).forEach((threadCount) -> {
sundaramPrimeStrategy.setThreadCount(threadCount);
sundaramPrimeStrategy.destroy();
sundaramPrimeStrategy.construct();
IntStream.of(2, 3, 101, 100003).forEach((nonPrime) -> {
boolean isPrime = sundaramPrimeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking big prime " + nonPrime + " with " + threadCount + " threads").isTrue();
}
);
});
}
@Test
public void testIsNotPrimeWithDifferentNumberOfThreads() {
IntStream.of(1, 2, 3, 100).forEach((threadCount) -> {
sundaramPrimeStrategy.setThreadCount(threadCount);
sundaramPrimeStrategy.destroy();
sundaramPrimeStrategy.construct();
IntStream.of(0, 1, 4, 100000).forEach((nonPrime) -> {
boolean isPrime = sundaramPrimeStrategy.isPrime(nonPrime);
assertThat(isPrime).as("checking non big prime " + nonPrime + " with " + threadCount + " threads").isFalse();
}
);
});
}
}
\ No newline at end of file
spring.main.show_banner: false
management.port: 8081
management.context-path: /manage
server.port: 8081
primes.sundaram.thread.count: 10
logging.file: prime-numbers.log
logging.level.uk.co.mits4u.primes: DEBUG
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment