Skip to content

Commit

Permalink
update 3rd party dependencies and refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
philippst committed May 24, 2019
1 parent 558f9ed commit 630b892
Show file tree
Hide file tree
Showing 14 changed files with 398 additions and 486 deletions.
45 changes: 19 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@

## Dependencies

| group | artifact | version |
| ------------------------- | ---------------- | ------- |
| org.apache.httpcomponents | httpclient | 4.5.3 |
| com.google.code.gson | gson | 2.8.1 |
| com.google.guava | guava | 23.0 |
| org.slf4j | slf4j-api | 1.7.25 |
| org.slf4j | jcl-over-slf4j | 1.7.25 |
| group | artifact | version |
| ------------------------- | ---------------- | -------- |
| com.squareup.okhttp3 | okhttp | 3.12.3 |
| com.google.code.gson | gson | 2.8.5 |
| com.google.guava | guava | 27.1-jre |
| org.slf4j | jcl-over-slf4j | 1.7.26 |

## Installation

Die Library ist als Maven Projekt ausgelegt und sollte als Abhängigkeit hinzugefügt werden.
Die Library ist als Maven POM Projekt ausgelegt und kann als Abhängigkeit hinzugefügt werden.
```xml
<dependencies>
<dependency>
<groupId>de.pfadfinden</groupId>
<artifactId>ica-services</artifactId>
<version>1.3-SNAPSHOT</version>
<version>2.0.0</version>
</dependency>
<dependencies>
```
Expand All @@ -28,30 +27,28 @@ Die Library ist als Maven Projekt ausgelegt und sollte als Abhängigkeit hinzuge
## Dokumentation

### Verbindungsaufbau und Authentifizierung
Mit Instanzierung eines IcaConnectors wird eine HTTP Session aufgebaut und die Authentifizierung an der ICA API
vorgenommen.

Eine Instanz des IcaConnectors kann für ein oder mehrere Serviceaufrufe verwendet werden. Dadurch entfällt der Overhead
für erneuten Aufbau der Session. Bei längerer Nicht-Benutzung kann die Session jedoch auslaufen.
Mit Instanzierung einer `IcaConnection` wird eine HTTP Session aufgebaut und die Authentifizierung an der ICA API
vorgenommen. Eine Instanz kann für ein oder mehrere Serviceaufrufe verwendet werden. Dadurch entfällt der Overhead
für erneute Authentifizierung und Aufbau der Session. Bei längerer Nicht-Benutzung kann die Session jedoch verfallen.

```java
IcaConnector icaConnector = new IcaConnector(IcaServer.BDP_QA,"username","password");
IcaConnection icaConnection = new IcaConnection(IcaServer.BDP_QA,"username","password");
```

Um die API Session sicher zu beenden und die Ressourcen des HTTP Clients freizugeben, muss der IcaConnector nach
Um die API Session sicher zu beenden und die Ressourcen des HTTP Clients freizugeben, sollte die IcaConnection nach
Verwendung geschlossen werden.

```java
// Nutzung für ein oder mehrere Serviceaufrufe
icaConnector.close();
icaConnection.close();
```

Da `java.io.Closeable` implementiert wird, kann alternativ zum manuellen `close` auch das try-with-resources
Statement verwendet werden.

```java
try(
IcaConnector icaConnector = new IcaConnector(icaServer,icaCredentials);
IcaConnection icaConnection = new IcaConnection(icaServer,icaCredentials);
){
// Nutzung für ein oder mehrere Serviceaufrufe
}
Expand All @@ -61,12 +58,12 @@ Sollte bereits über einen anderen Weg die Authentifizierung an der MV erfolgt s
mit einer SessionId erfolgen.

```java
IcaConnector icaConnector = new IcaConnector(IcaServer.BDP_QA,"XXX_SESSION_STRING_XXX");
IcaConnection icaConnection = new IcaConnection(IcaServer.BDP_QA,"XXX_SESSION_STRING_XXX");
```

### API Zugriffslimit
Die Vereinbarung zur Nutzung der BdP MV API sieht eine Beschränkung der Anzahl von maschinellen Zugriffe je Sekunde
vor. Die Klasse `IcaConnector` sieht in den Zugriffsmethoden ein entsprechendes Ratelimitung vor. Bei Überschreitung
vor. Die Klasse `IcaConnection` sieht in den Zugriffsmethoden ein entsprechendes Ratelimitung vor. Bei Überschreitung
der zulässigen Zugriffe werden API Anfragen verzögert, um ein temporäre Sperrung der genutzten IP Adresse zu vermeiden.

### Logging
Expand All @@ -75,7 +72,7 @@ Library sollte deshalb ein SLF4J kompatibles Logging Framework (z.B. Logback) od
einbinden.

### Serviceaufruf
Sämtliche Serviceaufrufe benötigen eine Instanz des `IcaConnector` im Konstruktor.
Sämtliche Serviceaufrufe benötigen eine Instanz des `IcaConnection` im Konstruktor.

#### MitgliedService
Der einfachste Anwendungsfall des Mitgliedservice ist die Abfrage der Mitgliedsdaten zu einer eindeutigen
Expand Down Expand Up @@ -111,11 +108,7 @@ Kind-Gruppierungen in diesem Baum gespeichert.


```java
MitgliedService mitgliedService = new MitgliedService(icaConnector);
Optional<IcaMitglied> mitglied = mitgliedService.getMitgliedById(11111);
mitglied.ifPresent(
icaMitglied -> System.out.println(icaMitglied.getNachname())
);

```


Expand Down
43 changes: 22 additions & 21 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

<groupId>de.pfadfinden</groupId>
<artifactId>ica-services</artifactId>
<version>1.11-SNAPSHOT</version>
<version>2.0.0</version>

<name>BdP ICA Services</name>
<name>ica-services</name>
<description>Java Library zur BdP Mitgliederverwaltung ICA.</description>
<url>https://github.com/pfadfinden/ica-services</url>

Expand All @@ -17,6 +17,19 @@
<url>http://www.pfadfinden.de</url>
</organization>

<licenses>
<license>
<name>The Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>

<scm>
<connection>scm:git:git://github.com/pfadfinden/ica-services.git</connection>
<developerConnection>scm:git:ssh://github.com:pfadfinden/ica-services.git</developerConnection>
<url>http://github.com/pfadfinden/ica-services/tree/master</url>
</scm>

<developers>
<developer>
<id>philippst</id>
Expand All @@ -31,53 +44,41 @@
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.test.skip>true</maven.test.skip>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.12.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>26.0-jre</version>
<version>27.1-jre</version>
</dependency>

<!-- SCOPE: TEST -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<version>1.7.26</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
195 changes: 195 additions & 0 deletions src/main/java/de/pfadfinden/ica/IcaConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package de.pfadfinden.ica;

import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import de.pfadfinden.ica.converter.LocalDateConverter;
import de.pfadfinden.ica.converter.LocalDateTimeConverter;
import de.pfadfinden.ica.converter.LocalTimeConverter;
import de.pfadfinden.ica.execption.IcaApiException;
import de.pfadfinden.ica.execption.IcaAuthenticationException;
import de.pfadfinden.ica.model.IcaApiResponse;
import de.pfadfinden.ica.model.IcaResponse;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull;

public class IcaConnection implements Closeable {

private OkHttpClient okHttpClient;

private Gson gson;

private final Logger logger = LoggerFactory.getLogger(IcaConnection.class);
private final RateLimiter apiRateLimiter = RateLimiter.create(3.0); // rate is "3 permits per second"

private CookieJar cookieJar = new MemoryCookieJar();

private IcaServer icaServer;

private static final String URL_STARTUP = "rest/nami/auth/manual/sessionStartup";

public IcaConnection(IcaServer icaServer, String username, String password) throws IcaAuthenticationException {
this.init(icaServer);

try {
authenticate(username, password);
} catch (IOException e) {
e.printStackTrace();
}
}

public IcaConnection(IcaServer icaServer, String session) {
this.init(icaServer);

Cookie cookie = new Cookie.Builder()
.domain(icaServer.getHost())
.path("/ica")
.name("JSESSIONID")
.value(session)
.secure()
.build();

// ToDo: httpURL!
this.cookieJar.saveFromResponse(null, Collections.singletonList(cookie));
}

private void init(IcaServer icaServer){
gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class, new LocalDateConverter())
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeConverter())
.registerTypeAdapter(LocalTime.class, new LocalTimeConverter())
.create();

this.icaServer = icaServer;

this.okHttpClient = new OkHttpClient.Builder()
.cookieJar(this.cookieJar)
.build();
}

private void authenticate(String username, String password) throws IOException, IcaAuthenticationException {

checkNotNull(emptyToNull(username),"username null or empty");
checkNotNull(emptyToNull(password),"password null or empty");

this.apiRateLimiter.acquire(); // may wait

HttpUrl httpUrl = this.getUrlBuilder(false)
.addPathSegments(URL_STARTUP)
.build();

RequestBody formBody = new FormBody.Builder()
.add("username", username)
.add("password", password)
.add("Login", "API")
.add("redirectTo", "./pages/loggedin.jsp")
.build();

Request request = new Request.Builder()
.url(httpUrl)
.post(formBody)
.build();

logger.debug("Authenticating on ICA via URI '{}'.",httpUrl);

try (
Response response = okHttpClient.newCall(request).execute()
) {
if (response.code() == 200 && response.body() != null) {
String resultData = response.body().string();
IcaApiResponse<Object> resp = gson.fromJson(resultData, new TypeToken<IcaApiResponse<Object>>() {}.getType());
logger.debug("Security: Authenticated to ICA using API token: {}.",resp.getApiSessionToken());
} else {
logger.warn("Security: Authentication on ICA failed with response code {}.",response.code());
throw new IcaAuthenticationException("Authentication on ICA failed.");
}
}
}

public <T> T executeApiRequest(HttpUrl httpUrl, Type resultType) throws IcaApiException {
Request request = new Request.Builder()
.url(httpUrl)
.build();
return this.executeApiRequest(request,resultType);
}

public <T> T executeApiRequest(Request request, Type resultType) throws IcaApiException {

this.apiRateLimiter.acquire(); // may wait

logger.info("Executing ICA API request. URI: '{}' Method: '{}' ResultType: '{}'",
request.url(),
request.method(),
resultType.getTypeName()
);

try (
Response response = okHttpClient.newCall(request).execute()
) {
if(response.code() != 200){
logger.error("ICA API returns error: RequestURI {} RequestType {} API ResultStatusCode {}",
request.url(),
request.method(),
response.code()
);
}

Type typeWithResponse = IcaResponse.getType(resultType);
Type typeWithApiResponse = IcaApiResponse.getType(typeWithResponse);

IcaApiResponse<IcaResponse<T>> result = gson.fromJson(response.body().string(), typeWithApiResponse);

if (result == null || result.getResponse() == null || result.getStatusCode() != 0) {
logger.error("Ica API returns error: RequestURI {} RequestType {} API ResultStatusCode {}",
request.url(),
request.method(),
response.code()
);
throw new IcaApiException(result);
}

return result.getResponse().getData();
} catch (IOException e) {
throw new IcaApiException(e);
}
}

public HttpUrl.Builder getUrlBuilder(){
return this.getUrlBuilder(true);
}

public HttpUrl.Builder getUrlBuilder(Boolean apiEndpoint){
HttpUrl.Builder builder = new HttpUrl.Builder()
.host(icaServer.getHost())
.addPathSegment(icaServer.getDeployment())
.scheme("https");
if(apiEndpoint) builder.addPathSegments("rest/api/1/2/service");
return builder;
}

public OkHttpClient getCloseableHttpClient() {
return this.okHttpClient;
}

public String toJson(Object o) {
return gson.toJson(o);
}

@Override
public void close() {

}
}
Loading

0 comments on commit 630b892

Please sign in to comment.