Working project.

This commit is contained in:
Mariusz Pacek committed 2018-03-11 23:37:59 +01:00
1 parent 587ab90d75
commit f3f42628ea
15 files changed
+522 -9

No files matched your search

+44
View File
@@ -12,6 +12,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<ratpack.version>1.5.4</ratpack.version>
<slf4j-api.version>1.7.25</slf4j-api.version>
</properties>
<dependencies>
@@ -25,6 +26,26 @@
<artifactId>ratpack-test</artifactId>
<version>${ratpack.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.tomakehurst/wiremock -->
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.15.0</version>
<type>pom</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -38,6 +59,29 @@
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<mainClass>edu.allegro.exercise.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>install</phase> <!-- bind to the install phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,28 @@
package edu.allegro.exercise;
public enum Constant {
APP_CONFIG("/app"),
GITHUB_USER_AGENT_HEADER("githubUserAgent"),
GITHUB_ACCEPT_HEADER("githubAcept"),
GITHUB_REPOSITORY_OWNER_INFO("githubResourceV3"),
ACCEPT("Accept"),
USER_AGENT("User-Agent"),
CONFIG_YAML("config.yaml"),
GITHUB_USER("githubUser"),
GITHUB_PASSWORD("githubPassword"), CLIENT_POOL_SIZE("clientPoolSize"), CLIENT_TIMEOUT("clientTimeout");
private final String prop;
Constant(String prop) {
this.prop = prop;
}
public String getName() {
return prop;
}
@Override
public String toString() {
return prop;
}
}
@@ -0,0 +1,28 @@
package edu.allegro.exercise;
import edu.allegro.exercise.handlers.GetUserRepoInfoService;
import edu.allegro.exercise.handlers.RepositoryInfo;
import edu.allegro.exercise.util.ServerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ratpack.server.ServerConfig;
import java.util.Map;
public class Main {
static final private Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) throws Exception {
ServerConfig config = ServerConfig
.builder()
.yaml(ServerUtil.getConfigurationFile())
.onError(throwable -> log.error("Cannot load config file", throwable))
.build();
Map<String, Object> configuration = ServerUtil.getConfiguration(config);
RepositoryInfo info = new RepositoryInfo(configuration);
GetUserRepoInfoService getUserRepoInfoService = new GetUserRepoInfoService(info);
Server.start(config, getUserRepoInfoService);
}
}
@@ -0,0 +1,27 @@
package edu.allegro.exercise;
import edu.allegro.exercise.model.github.ErrorResponse;
public enum ResultCodes {
BAD_REQUEST(400, 500, "Internal error - Bad github request"),
NOT_FOUND(404, 404, "Not found - Given repository or owner doesn't exist");
private final int statusCode;
private final int internalCode;
private final String statusDescription;
ResultCodes(int statusCode, int internalCode, String statusDescription) {
this.statusCode = statusCode;
this.statusDescription = statusDescription;
this.internalCode = internalCode;
}
public static ErrorResponse fromCode(int statusCode) {
for (ResultCodes resultCodes : ResultCodes.values()) {
if (resultCodes.statusCode == statusCode) {
return new ErrorResponse(resultCodes.internalCode, resultCodes.statusDescription);
}
}
return new ErrorResponse(500, "Server internal error");
}
}
@@ -0,0 +1,37 @@
package edu.allegro.exercise;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import edu.allegro.exercise.handlers.GetUserRepoInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ratpack.error.ClientErrorHandler;
import ratpack.error.ServerErrorHandler;
import ratpack.error.internal.DefaultProductionErrorHandler;
import ratpack.handling.RequestLogger;
import ratpack.registry.Registry;
import ratpack.server.RatpackServer;
import ratpack.server.ServerConfig;
public class Server {
private static final Logger log = LoggerFactory.getLogger(Server.class);
static void start(ServerConfig config, GetUserRepoInfoService userRepoInfo) throws Exception {
RatpackServer.start(
ratpackServerSpec -> ratpackServerSpec
.serverConfig(config)
.registry(Registry.builder()
.add(ClientErrorHandler.class, new DefaultProductionErrorHandler())
.add(ServerErrorHandler.class, new DefaultProductionErrorHandler())
.with(registrySpec ->
registrySpec.add(new ObjectMapper().registerModule(new Jdk8Module())))
.build())
.handlers(userRepoInfo.create().append(
chain ->
chain.all((RequestLogger.ncsa()))
)
)
);
}
}
@@ -0,0 +1,81 @@
package edu.allegro.exercise.handlers;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.allegro.exercise.ResultCodes;
import edu.allegro.exercise.model.github.ErrorResponse;
import edu.allegro.exercise.model.github.UserRepoInfo;
import io.netty.handler.codec.http.HttpResponseStatus;
import ratpack.exec.Promise;
import ratpack.func.Action;
import ratpack.func.Function;
import ratpack.handling.Chain;
import ratpack.handling.Context;
import ratpack.http.client.ReceivedResponse;
import ratpack.jackson.Jackson;
import ratpack.jackson.JsonRender;
import java.io.IOException;
import java.util.Optional;
import static ratpack.jackson.Jackson.json;
public class GetUserRepoInfoService {
private static final String OWNER = "owner";
private static final String REPO = "repo";
private static final String PATH = ":" + OWNER + "/:" + REPO;
private static final String REPOSITORIES = "repositories";
private final RepositoryInfo repositoryInfo;
private final ObjectMapper mapper;
public GetUserRepoInfoService(RepositoryInfo repositoryInfo) {
this.repositoryInfo = repositoryInfo;
this.mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public Action<Chain> create() {
return chain ->
chain.prefix(REPOSITORIES, action ->
action.get(PATH, ctx -> {
Promise<Optional<ReceivedResponse>> optionalPromise =
repositoryInfo.getInfo(getToken(ctx, OWNER), getToken(ctx, REPO))
.mapIf(this::ifSuccess, handleOK(), handleNOK(ctx));
Promise<JsonRender> response = optionalPromise
.map(responseOp -> responseOp.map(this::getUserRepoInfo))
.map(Jackson::json);
ctx.render(response);
}));
}
private boolean ifSuccess(ReceivedResponse response) {
return response.getStatusCode() == HttpResponseStatus.OK.code();
}
private Function<ReceivedResponse, Optional<ReceivedResponse>> handleOK() {
return Optional::of;
}
private Function<ReceivedResponse, Optional<ReceivedResponse>> handleNOK(Context ctx) {
return response -> {
ErrorResponse errorResponse = ResultCodes.fromCode(response.getStatusCode());
ctx.getResponse().status(errorResponse.getStatusCode());
ctx.render(json(errorResponse));
return Optional.empty();
};
}
private String getToken(Context ctx, String token) {
return ctx.getPathTokens().get(token);
}
private Optional<UserRepoInfo> getUserRepoInfo(ReceivedResponse res) {
try {
return Optional.of(mapper.readValue(res.getBody().getText(), UserRepoInfo.class));
} catch (IOException e) {
return Optional.empty();
}
}
}
@@ -0,0 +1,79 @@
package edu.allegro.exercise.handlers;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.util.internal.StringUtil;
import ratpack.exec.Promise;
import ratpack.http.Headers;
import ratpack.http.client.HttpClient;
import ratpack.http.client.ReceivedResponse;
import ratpack.http.client.RequestSpec;
import ratpack.http.internal.NettyHeadersBackedHeaders;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;
import static edu.allegro.exercise.Constant.*;
public class RepositoryInfo {
private static final String AUTHORIZATION = "Authorization";
private static final String BASIC = "Basic ";
private final HttpClient client;
private final String resourceLink;
private final Headers headers;
public RepositoryInfo(Map<String, Object> configuration) throws Exception {
Integer poolSize = (Integer) configuration.get(CLIENT_POOL_SIZE.getName());
Integer timeout = (Integer) configuration.get(CLIENT_TIMEOUT.getName());
this.client = initHttpClient(poolSize, timeout);
this.resourceLink = (String) configuration.get(GITHUB_REPOSITORY_OWNER_INFO.getName());
this.headers = setHeaders(configuration);
}
private HttpClient initHttpClient(int poolSize, int timeout) throws Exception {
return HttpClient.of(httpClientSpec ->
httpClientSpec
.poolSize(poolSize)
.connectTimeout(Duration.ofSeconds(timeout))
.readTimeout(Duration.ofSeconds(timeout))
);
}
public Promise<ReceivedResponse> getInfo(String owner, String repo) throws URISyntaxException {
StringBuilder urlBuilder = new StringBuilder(resourceLink)
.append(owner)
.append("/")
.append(repo);
return client.get(new URI(urlBuilder.toString()),
this::setHeaders);
}
private void setHeaders(RequestSpec requestSpec) {
requestSpec.getHeaders().copy(headers);
}
private Headers setHeaders(Map<String, Object> configuration) {
DefaultHttpHeaders defaultHttpHeaders = new DefaultHttpHeaders();
Headers headers = new NettyHeadersBackedHeaders(defaultHttpHeaders);
defaultHttpHeaders.set(ACCEPT.getName(), configuration.get(GITHUB_ACCEPT_HEADER.getName()));
defaultHttpHeaders.set(USER_AGENT.getName(), configuration.get(GITHUB_USER_AGENT_HEADER.getName()));
setAuthorizationHeader(configuration, defaultHttpHeaders);
return headers;
}
private void setAuthorizationHeader(Map<String, Object> configuration, DefaultHttpHeaders defaultHttpHeaders) {
String githubUser = (String) configuration.get(GITHUB_USER.getName());
String githubPassword = (String) configuration.get(GITHUB_PASSWORD.getName());
if (!StringUtil.isNullOrEmpty(githubPassword) && !StringUtil.isNullOrEmpty(githubPassword)) {
String credentials = githubUser + ":" + githubPassword;
byte[] encode = Base64.getEncoder().encode(credentials.getBytes());
defaultHttpHeaders.set(AUTHORIZATION, BASIC + new String(encode));
}
}
}
@@ -0,0 +1,19 @@
package edu.allegro.exercise.model.github;
public class ErrorResponse {
private int StatusCode;
private String StatusDescription;
public ErrorResponse(int statusCode, String statusDescription) {
StatusCode = statusCode;
StatusDescription = statusDescription;
}
public int getStatusCode() {
return StatusCode;
}
public String getStatusDescription() {
return StatusDescription;
}
}
@@ -0,0 +1,30 @@
package edu.allegro.exercise.model.github;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"full_name",
"description",
"created_at",
"clone_url",
"stargazers_count",
})
public class UserRepoInfo {
@JsonProperty("full_name")
private String fullName;
@JsonProperty("description")
private String description;
@JsonProperty("created_at")
private String createdAt;
@JsonProperty("clone_url")
private String cloneUrl;
@JsonProperty("stargazers_count")
private Integer stargazersCount;
}
@@ -0,0 +1,28 @@
package edu.allegro.exercise.util;
import ratpack.server.ServerConfig;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import static edu.allegro.exercise.Constant.APP_CONFIG;
import static edu.allegro.exercise.Constant.CONFIG_YAML;
public class ServerUtil {
@SuppressWarnings("unchecked")
public static Map<String, Object> getConfiguration(ServerConfig config) {
return config.get(APP_CONFIG.getName(), Map.class);
}
public static URL getConfigurationFile() throws MalformedURLException {
File file = new File(CONFIG_YAML.getName());
if (file.exists()) {
return file.toURI().toURL();
} else {
return Thread.currentThread().getContextClassLoader().getResource(CONFIG_YAML.getName());
}
}
}
@@ -1,9 +0,0 @@
package edu.allegro.exercisie;
public class Main {
public static void main(String[] args) {
}
}
+13
View File
@@ -0,0 +1,13 @@
server:
port: 8080
threads: 1
development: false
app:
clientPoolSize: 20
clientTimeout: 10
githubResourceV3: https://api.github.com/repos/
githubUserAgent: Allegro-exercise
githubAcept: application/vnd.github.v3+json
githubUser: sinu0
githubPassword: myk700i
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="MyFile" fileName="all.log" immediateFlush="false" append="true">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="Info">
<AppenderRef ref="Console"/>
<AppenderRef ref="MyFile"/>
</Root>
</Loggers>
</Configuration>
@@ -0,0 +1,20 @@
package edu.allegro.exercise.handlers;
import java.util.HashMap;
import java.util.Map;
public class GetUserRepoInfoTest {
private static final Map<String, Object> CONFIGURATION = new HashMap<String, Object>() {{
put("githubResourceV3", "https://api.github.com/repos/%s/%s");
put("githubUserAgent", "Allegro-exercise");
put("githubAcept", "application/vnd.github.v3+json");
}};
public static GetUserRepoInfoService userRepotTest() throws Exception {
return new GetUserRepoInfoService(CONFIGURATION);
}
}
@@ -0,0 +1,71 @@
package edu.allegro.exercise.handlers;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.*;
import ratpack.http.client.ReceivedResponse;
import ratpack.server.RatpackServer;
import ratpack.server.ServerConfigBuilder;
import ratpack.test.embed.EmbeddedApp;
import ratpack.test.http.TestHttpClient;
public class IntegrationTest {
private static final String GITHUB_OWNER = "sinu0";
private static final String GIHTUB_REPO = "SiSPD";
private static final String GIHTUB_REPO_NOK = "SiSPDs";
@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
private EmbeddedApp app;
@Before
public void setUp() throws Exception {
GetUserRepoInfoService getUserRepoInfoService = GetUserRepoInfoTest.userRepotTest();
app = EmbeddedApp.fromServer(RatpackServer.of(ratpackServerSpec ->
ratpackServerSpec
.serverConfig(ServerConfigBuilder::build)
.handlers(getUserRepoInfoService.create())));
}
@After
public void endTests() throws Exception {
app.getServer().stop();
app = null;
}
@Test
public void shouldReturnOKInfoAboutOwnerRepo() throws Exception {
app.test(this::shouldReturnOKInfoAboutOwnerRepo);
}
@Test
public void shouldReturnNOKInfoAboutOwnerRepo() throws Exception {
app.test(this::shouldReturnNOKInfoAboutOwnerRepo);
}
private void shouldReturnOKInfoAboutOwnerRepo(TestHttpClient testHttpClient) {
ReceivedResponse receivedResponse = testHttpClient.get(String.format("/repositories/%s/%s", GITHUB_OWNER, GIHTUB_REPO));
responseShouldBeValid(receivedResponse);
}
private void shouldReturnNOKInfoAboutOwnerRepo(TestHttpClient testHttpClient) {
ReceivedResponse receivedResponse = testHttpClient.get(String.format("/repositories/%s/%s", GITHUB_OWNER, GIHTUB_REPO_NOK));
responseShouldBeNotValid(receivedResponse);
}
private void responseShouldBeValid(ReceivedResponse receivedResponse) {
System.out.println(receivedResponse.getHeaders().asMultiValueMap().toString());
System.out.println(receivedResponse.getBody().getText());
Assert.assertTrue("Response should be ok for owner:" + GITHUB_OWNER + " and for repo: " + GIHTUB_REPO, receivedResponse.getStatusCode() == HttpResponseStatus.OK.code());
}
private void responseShouldBeNotValid(ReceivedResponse receivedResponse) {
System.out.println(receivedResponse.getHeaders().asMultiValueMap().toString());
System.out.println(receivedResponse.getBody().getText());
Assert.assertTrue("Response should be nok for owner:" + GITHUB_OWNER + " and for repo: " + GIHTUB_REPO_NOK, receivedResponse.getStatusCode() != HttpResponseStatus.OK.code());
}
private void performanceTest() {
}
}