Skip to content

Commit cd3169e

Browse files
authoredMar 24, 2025··
Replace retrofit with Apache HTTP Component (#205)
* Replace retrofit with apache http.
1 parent 60789a1 commit cd3169e

File tree

9 files changed

+209
-153
lines changed

9 files changed

+209
-153
lines changed
 

‎docs/docs/filter_policies/filters/persons_names/ph-eye.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ This filter requires the connection properties for the ph-eye service in a `phEy
1111

1212
| Parameter | Description | Default Value |
1313
|------------|-------------------------------------------------------------------|---------------------------|
14-
| `endpoint` | The ph-eye service endpoint. | `http://localhost:18080/` |
15-
| `username` | The ph-eye service username. | None |
16-
| `password` | The ph-eye service endpoint. | None |
14+
| `endpoint` | The ph-eye service endpoint. | `http://localhost:18080` |
1715
| `timeout` | The ph-eye service connection timeout in seconds. | `600` |
1816
| `labels` | A comma-separated list of labels supported by the ph-eye service. | `Person` |
1917

@@ -23,6 +21,7 @@ This filter requires the connection properties for the ph-eye service in a `phEy
2321
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
2422
| `removePunctuation` | When set to true, punctuation will be removed prior to analysis. | `false` |
2523
| `phEyeFilterStrategies` | A list of filter strategies. | None |
24+
| `bearerToken` | A bearer token for the Ph-Eye service. | None |
2625
| `enabled` | When set to false, the filter will be disabled and not applied | `true` |
2726
| `ignored` | A list of terms to be ignored by the filter. | None |
2827
| `windowSize` | Sets the size of the window (in terms) surrounding a span to look for contextual terms. If set, this value overrides the value of `span.window.size` in the configuration. | The value of `span.window.size` which is by default `5`. |
@@ -61,7 +60,7 @@ Each filter strategy may have one condition. See [Conditions](#conditions) for d
6160
"identifiers": {
6261
"pheye": {
6362
"phEyeConfiguration": {
64-
"endpoint": "http://localhost:18080/"
63+
"endpoint": "http://localhost:18080"
6564
},
6665
"pheyeFilterStrategies": [
6766
{

‎phileas-core/pom.xml

+3-13
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,9 @@
6363
<version>${gson.version}</version>
6464
</dependency>
6565
<dependency>
66-
<groupId>com.squareup.okhttp3</groupId>
67-
<artifactId>okhttp</artifactId>
68-
<version>${okhttp.version}</version>
69-
</dependency>
70-
<dependency>
71-
<groupId>com.squareup.retrofit2</groupId>
72-
<artifactId>converter-gson</artifactId>
73-
<version>${retrofit.version}</version>
74-
</dependency>
75-
<dependency>
76-
<groupId>com.squareup.retrofit2</groupId>
77-
<artifactId>converter-scalars</artifactId>
78-
<version>${retrofit.version}</version>
66+
<groupId>org.apache.httpcomponents.client5</groupId>
67+
<artifactId>httpclient5</artifactId>
68+
<version>${httpclient.version}</version>
7969
</dependency>
8070
<dependency>
8171
<groupId>org.apache.commons</groupId>

‎phileas-core/src/main/java/ai/philterd/phileas/services/FilterPolicyLoader.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -1188,8 +1188,7 @@ public List<Filter> getFiltersForPolicy(final Policy policy, final Map<String, M
11881188
phEyeConfiguration.setTimeout(policy.getIdentifiers().getPhEye().getPhEyeConfiguration().getTimeout());
11891189
phEyeConfiguration.setKeepAliveDurationMs(policy.getIdentifiers().getPhEye().getPhEyeConfiguration().getKeepAliveDurationMs());
11901190
phEyeConfiguration.setMaxIdleConnections(policy.getIdentifiers().getPhEye().getPhEyeConfiguration().getMaxIdleConnections());
1191-
phEyeConfiguration.setUsername(policy.getIdentifiers().getPhEye().getPhEyeConfiguration().getUsername());
1192-
phEyeConfiguration.setPassword(policy.getIdentifiers().getPhEye().getPhEyeConfiguration().getPassword());
1191+
phEyeConfiguration.setBearerToken(policy.getIdentifiers().getPhEye().getPhEyeConfiguration().getBearerToken());
11931192

11941193
final Filter filter = new PhEyeFilter(
11951194
filterConfiguration,

‎phileas-core/src/main/java/ai/philterd/phileas/services/filters/ai/pheye/PhEyeConfiguration.java

+5-14
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
public class PhEyeConfiguration {
2424

2525
private String endpoint;
26-
private String username;
27-
private String password;
26+
private String bearerToken;
2827
private int timeout;
2928
private int maxIdleConnections;
3029
private int keepAliveDurationMs;
@@ -78,20 +77,12 @@ public void setLabels(Collection<String> labels) {
7877
this.labels = labels;
7978
}
8079

81-
public String getUsername() {
82-
return username;
80+
public String getBearerToken() {
81+
return bearerToken;
8382
}
8483

85-
public void setUsername(String username) {
86-
this.username = username;
87-
}
88-
89-
public String getPassword() {
90-
return password;
91-
}
92-
93-
public void setPassword(String password) {
94-
this.password = password;
84+
public void setBearerToken(String bearerToken) {
85+
this.bearerToken = bearerToken;
9586
}
9687

9788
}

‎phileas-core/src/main/java/ai/philterd/phileas/services/filters/ai/pheye/PhEyeFilter.java

+97-70
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,28 @@
2323
import ai.philterd.phileas.model.objects.Span;
2424
import ai.philterd.phileas.model.policy.Policy;
2525
import ai.philterd.phileas.model.services.MetricsService;
26-
2726
import com.google.gson.Gson;
2827
import com.google.gson.reflect.TypeToken;
29-
import okhttp3.Authenticator;
30-
import okhttp3.ConnectionPool;
31-
import okhttp3.Credentials;
32-
import okhttp3.OkHttpClient;
33-
import okhttp3.Request;
34-
import okhttp3.Route;
3528
import org.apache.commons.collections4.CollectionUtils;
3629
import org.apache.commons.lang3.StringUtils;
3730
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
31+
import org.apache.hc.client5.http.classic.methods.HttpPost;
32+
import org.apache.hc.client5.http.config.RequestConfig;
33+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
34+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
35+
import org.apache.hc.client5.http.impl.classic.HttpClients;
36+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
37+
import org.apache.hc.core5.http.HttpEntity;
38+
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
39+
import org.apache.hc.core5.http.io.entity.EntityUtils;
40+
import org.apache.hc.core5.http.io.entity.StringEntity;
41+
import org.apache.hc.core5.net.URIBuilder;
3842
import org.apache.logging.log4j.LogManager;
3943
import org.apache.logging.log4j.Logger;
40-
import retrofit2.Response;
41-
import retrofit2.Retrofit;
42-
import retrofit2.converter.gson.GsonConverterFactory;
43-
import retrofit2.converter.scalars.ScalarsConverterFactory;
4444

4545
import java.io.IOException;
4646
import java.lang.reflect.Type;
47+
import java.net.URI;
4748
import java.util.ArrayList;
4849
import java.util.Collection;
4950
import java.util.LinkedList;
@@ -57,9 +58,11 @@ public class PhEyeFilter extends NerFilter {
5758

5859
private final boolean removePunctuation;
5960

60-
private final transient PhEyeService service;
61+
private final PhEyeConfiguration phEyeConfiguration;
6162
private final Collection<String> labels;
62-
63+
private final Gson gson;
64+
final PoolingHttpClientConnectionManager connectionManager;
65+
6366
public PhEyeFilter(final FilterConfiguration filterConfiguration,
6467
final PhEyeConfiguration phEyeConfiguration,
6568
final Map<String, DescriptiveStatistics> stats,
@@ -69,53 +72,35 @@ public PhEyeFilter(final FilterConfiguration filterConfiguration,
6972

7073
super(filterConfiguration, stats, metricsService, thresholds, FilterType.AGE);
7174

75+
this.phEyeConfiguration = phEyeConfiguration;
7276
this.removePunctuation = removePunctuation;
7377
this.labels = phEyeConfiguration.getLabels();
78+
this.gson = new Gson();
7479

75-
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
80+
this.connectionManager = new PoolingHttpClientConnectionManager();
7681

77-
if(StringUtils.isNotEmpty(phEyeConfiguration.getUsername()) && StringUtils.isNotEmpty(phEyeConfiguration.getPassword())) {
78-
builder.authenticator(new Authenticator() {
79-
@Override
80-
public Request authenticate(final Route route, final okhttp3.Response response) {
81-
final String credential = Credentials.basic(phEyeConfiguration.getUsername(), phEyeConfiguration.getPassword());
82-
return response.request().newBuilder().header("Authorization", credential).build();
83-
}
84-
});
82+
if(phEyeConfiguration.getMaxIdleConnections() > 0) {
83+
connectionManager.setMaxTotal(phEyeConfiguration.getMaxIdleConnections());
84+
connectionManager.setDefaultMaxPerRoute(phEyeConfiguration.getMaxIdleConnections());
8585
}
8686

87-
builder.retryOnConnectionFailure(true);
88-
builder.connectTimeout(phEyeConfiguration.getTimeout(), TimeUnit.SECONDS);
89-
builder.writeTimeout(phEyeConfiguration.getTimeout(), TimeUnit.SECONDS);
90-
builder.readTimeout(phEyeConfiguration.getTimeout(), TimeUnit.SECONDS);
91-
builder.connectionPool(new ConnectionPool(phEyeConfiguration.getMaxIdleConnections(), phEyeConfiguration.getKeepAliveDurationMs(), TimeUnit.MILLISECONDS));
92-
93-
final OkHttpClient okHttpClient = builder.build();
94-
95-
final Retrofit retrofit = new Retrofit.Builder()
96-
.baseUrl(phEyeConfiguration.getEndpoint())
97-
.client(okHttpClient)
98-
.callFactory(okHttpClient)
99-
.addConverterFactory(ScalarsConverterFactory.create())
100-
.addConverterFactory(GsonConverterFactory.create())
101-
.build();
102-
103-
service = retrofit.create(PhEyeService.class);
104-
10587
}
10688

10789
@Override
10890
public FilterResult filter(final Policy policy, final String context, final String documentId, final int piece,
109-
String input, final Map<String, String> attributes) throws Exception {
91+
final String input, final Map<String, String> attributes) throws Exception {
11092

11193
final List<Span> spans = new LinkedList<>();
11294

11395
// Remove punctuation if instructed to do so.
11496
// It is replacing each punctuation mark with an empty space. This will allow span indexes
11597
// to remain constant as opposed to removing the punctuation and causing the string to then
11698
// have a shorter length.
99+
final String formattedInput;
117100
if(removePunctuation) {
118-
input = input.replaceAll("\\p{Punct}", " ");
101+
formattedInput = input.replaceAll("\\p{Punct}", " ");
102+
} else {
103+
formattedInput = input;
119104
}
120105

121106
final PhEyeRequest phEyeRequest = new PhEyeRequest();
@@ -124,34 +109,77 @@ public FilterResult filter(final Policy policy, final String context, final Stri
124109
phEyeRequest.setDocumentId(documentId);
125110
phEyeRequest.setPiece(piece);
126111
phEyeRequest.setLabels(labels);
127-
128-
final Response<String> response = service.find(phEyeRequest).execute();
129-
130-
if(response.isSuccessful()) {
131112

132-
final Type listType = new TypeToken<ArrayList<PhEyeSpan>>(){}.getType();
133-
final List<PhEyeSpan> phEyeSpans = new Gson().fromJson(response.body(), listType);
134-
135-
if (CollectionUtils.isNotEmpty(phEyeSpans)) {
113+
final String json = gson.toJson(phEyeRequest);
114+
115+
final URI uri = new URIBuilder(phEyeConfiguration.getEndpoint() + "/find")
116+
.build();
117+
118+
final RequestConfig requestConfig = RequestConfig.custom()
119+
.setConnectionRequestTimeout(phEyeConfiguration.getTimeout(), TimeUnit.SECONDS)
120+
.setResponseTimeout(phEyeConfiguration.getTimeout(), TimeUnit.SECONDS)
121+
.build();
122+
123+
final HttpPost httpPost = new HttpPost(uri);
124+
httpPost.setConfig(requestConfig);
125+
httpPost.setEntity(new StringEntity(json));
126+
httpPost.setHeader("Content-Type", "application/json");
127+
httpPost.setHeader("Accept", "application/json");
128+
129+
if(StringUtils.isNotEmpty(phEyeConfiguration.getBearerToken())) {
130+
httpPost.setHeader("Authorization", "Bearer " + phEyeConfiguration.getBearerToken());
131+
}
132+
133+
final HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager);
136134

137-
for (final PhEyeSpan phEyeSpan : phEyeSpans) {
135+
try(CloseableHttpClient httpClient = httpClientBuilder.build()) {
138136

139-
// Only interested in spans matching the tag we are looking for, e.g. PER, LOC.
140-
if (labels.contains(phEyeSpan.getLabel())) {
137+
final HttpClientResponseHandler<String> responseHandler = response -> {
141138

142-
// Check the confidence threshold.
143-
if(!thresholds.containsKey(phEyeSpan.getLabel().toUpperCase()) || phEyeSpan.getScore() >= thresholds.get(phEyeSpan.getLabel().toUpperCase())) {
139+
if (response.getCode() == 200) {
144140

145-
// Get the window of text surrounding the token.
146-
final String[] window = getWindow(input, phEyeSpan.getStart(), phEyeSpan.getEnd());
141+
final HttpEntity responseEntity = response.getEntity();
142+
return responseEntity != null ? EntityUtils.toString(responseEntity) : null;
147143

148-
final Span span = createSpan(policy, context, documentId, phEyeSpan.getText(),
149-
window, phEyeSpan.getLabel(), phEyeSpan.getStart(), phEyeSpan.getEnd(),
150-
phEyeSpan.getScore(), attributes);
144+
} else {
145+
146+
// The request to philter-ner was not successful.
147+
LOGGER.error("PhEyeFilter failed with code {}", response.getCode());
148+
throw new IOException("Unable to process document. Received error response from philter-ner.");
149+
150+
}
151+
152+
};
153+
154+
final String responseBody = httpClient.execute(httpPost, responseHandler);
155+
156+
if (responseBody != null) {
157+
158+
final Type listType = new TypeToken<ArrayList<PhEyeSpan>>() {}.getType();
159+
final List<PhEyeSpan> phEyeSpans = new Gson().fromJson(responseBody, listType);
160+
161+
if (CollectionUtils.isNotEmpty(phEyeSpans)) {
162+
163+
for (final PhEyeSpan phEyeSpan : phEyeSpans) {
164+
165+
// Only interested in spans matching the tag we are looking for, e.g. PER, LOC.
166+
if (labels.contains(phEyeSpan.getLabel())) {
167+
168+
// Check the confidence threshold.
169+
if (!thresholds.containsKey(phEyeSpan.getLabel().toUpperCase()) || phEyeSpan.getScore() >= thresholds.get(phEyeSpan.getLabel().toUpperCase())) {
170+
171+
// Get the window of text surrounding the token.
172+
final String[] window = getWindow(formattedInput, phEyeSpan.getStart(), phEyeSpan.getEnd());
173+
174+
final Span span = createSpan(policy, context, documentId, phEyeSpan.getText(),
175+
window, phEyeSpan.getLabel(), phEyeSpan.getStart(), phEyeSpan.getEnd(),
176+
phEyeSpan.getScore(), attributes);
177+
178+
// Span will be null if no span was created due to it being excluded.
179+
if (span != null) {
180+
spans.add(span);
181+
}
151182

152-
// Span will be null if no span was created due to it being excluded.
153-
if (span != null) {
154-
spans.add(span);
155183
}
156184

157185
}
@@ -160,16 +188,15 @@ public FilterResult filter(final Policy policy, final String context, final Stri
160188

161189
}
162190

163-
LOGGER.debug("Returning {} NER spans.", spans.size());
191+
LOGGER.debug("Returning {} NER spans from ph-eye.", spans.size());
192+
return new FilterResult(context, documentId, piece, spans);
164193

165-
}
166-
167-
return new FilterResult(context, documentId, piece, spans);
194+
} else {
168195

169-
} else {
196+
// We received null back which is not expected.
197+
throw new IOException("Unable to process document. Received error response from philter-ner.");
170198

171-
// The request to philter-ner was not successful.
172-
throw new IOException("Unable to process document. Received error response from philter-ner.");
199+
}
173200

174201
}
175202

‎phileas-core/src/main/java/ai/philterd/phileas/services/filters/ai/pheye/PhEyeService.java

-29
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2025 Philterd, LLC @ https://www.philterd.ai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License", attributes);
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package ai.philterd.test.phileas.services.filters;
17+
18+
import ai.philterd.phileas.model.cache.InMemoryCache;
19+
import ai.philterd.phileas.model.filter.FilterConfiguration;
20+
import ai.philterd.phileas.model.objects.FilterResult;
21+
import ai.philterd.phileas.model.services.AlertService;
22+
import ai.philterd.phileas.model.services.CacheService;
23+
import ai.philterd.phileas.model.services.MetricsService;
24+
import ai.philterd.phileas.services.anonymization.AgeAnonymizationService;
25+
import ai.philterd.phileas.services.filters.ai.pheye.PhEyeConfiguration;
26+
import ai.philterd.phileas.services.filters.ai.pheye.PhEyeFilter;
27+
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
28+
import org.junit.jupiter.api.Assertions;
29+
import org.junit.jupiter.api.Disabled;
30+
import org.junit.jupiter.api.Test;
31+
import org.mockito.Mockito;
32+
33+
import java.util.HashMap;
34+
import java.util.Map;
35+
36+
@Disabled("Disabled until this is an integration tests and there can be a ph-eye service running to test against.")
37+
public class PhEyeFilterTest extends AbstractFilterTest {
38+
39+
@Test
40+
public void filter1() throws Exception {
41+
42+
final AlertService alertService = Mockito.mock(AlertService.class);
43+
final CacheService cacheService = new InMemoryCache();
44+
45+
final PhEyeConfiguration phEyeConfiguration = new PhEyeConfiguration("http://localhost:5000");
46+
final Map<String, DescriptiveStatistics> stats = new HashMap<>();
47+
final MetricsService metricsService = Mockito.mock(MetricsService.class);
48+
final boolean removePunctuation = false;
49+
final Map<String, Double> thresholds = new HashMap<>();
50+
51+
final FilterConfiguration filterConfiguration = new FilterConfiguration.FilterConfigurationBuilder()
52+
.withAlertService(alertService)
53+
.withAnonymizationService(new AgeAnonymizationService(cacheService))
54+
.withWindowSize(windowSize)
55+
.build();
56+
57+
final PhEyeFilter filter = new PhEyeFilter(filterConfiguration, phEyeConfiguration, stats, metricsService, removePunctuation, thresholds);
58+
59+
final FilterResult filterResult = filter.filter(getPolicy(), "context", "documentid", PIECE, "George Washington was the first president.", attributes);
60+
61+
Assertions.assertEquals(1, filterResult.getSpans().size());
62+
Assertions.assertEquals("George Washington", filterResult.getSpans().iterator().next().getText());
63+
64+
}
65+
66+
@Test
67+
public void filter2() throws Exception {
68+
69+
final AlertService alertService = Mockito.mock(AlertService.class);
70+
final CacheService cacheService = new InMemoryCache();
71+
72+
final PhEyeConfiguration phEyeConfiguration = new PhEyeConfiguration("http://localhost:5000");
73+
final Map<String, DescriptiveStatistics> stats = new HashMap<>();
74+
final MetricsService metricsService = Mockito.mock(MetricsService.class);
75+
final boolean removePunctuation = false;
76+
final Map<String, Double> thresholds = new HashMap<>();
77+
78+
final FilterConfiguration filterConfiguration = new FilterConfiguration.FilterConfigurationBuilder()
79+
.withAlertService(alertService)
80+
.withAnonymizationService(new AgeAnonymizationService(cacheService))
81+
.withWindowSize(windowSize)
82+
.build();
83+
84+
final PhEyeFilter filter = new PhEyeFilter(filterConfiguration, phEyeConfiguration, stats, metricsService, removePunctuation, thresholds);
85+
86+
final FilterResult filterResult = filter.filter(getPolicy(), "context", "documentid", PIECE, "No name here was the first president.", attributes);
87+
88+
Assertions.assertEquals(0, filterResult.getSpans().size());
89+
90+
}
91+
92+
}

‎phileas-model/src/main/java/ai/philterd/phileas/model/policy/filters/pheye/PhEyeConfiguration.java

+6-18
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,9 @@ public class PhEyeConfiguration {
1212
@Expose
1313
protected String endpoint = "http://philter-ph-eye-1:5000/";
1414

15-
@SerializedName("username")
15+
@SerializedName("bearerToken")
1616
@Expose
17-
protected String username;
18-
19-
@SerializedName("password")
20-
@Expose
21-
protected String password;
17+
protected String bearerToken;
2218

2319
@SerializedName("timeout")
2420
@Expose
@@ -76,20 +72,12 @@ public void setLabels(Collection<String> labels) {
7672
this.labels = labels;
7773
}
7874

79-
public String getUsername() {
80-
return username;
81-
}
82-
83-
public void setUsername(String username) {
84-
this.username = username;
85-
}
86-
87-
public void setPassword(String password) {
88-
this.password = password;
75+
public String getBearerToken() {
76+
return bearerToken;
8977
}
9078

91-
public String getPassword() {
92-
return password;
79+
public void setBearerToken(String bearerToken) {
80+
this.bearerToken = bearerToken;
9381
}
9482

9583
}

‎pom.xml

+2-3
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@
8888
<commons.validator.version>1.9.0</commons.validator.version>
8989
<equals.verifier.version>3.19.1</equals.verifier.version>
9090
<ff3.version>1.2.0</ff3.version>
91-
<hapi.fhir.version>4.2.0</hapi.fhir.version>
9291
<gson.version>2.12.1</gson.version>
92+
<hapi.fhir.version>4.2.0</hapi.fhir.version>
93+
<httpclient.version>5.4.2</httpclient.version>
9394
<json.version>20250107</json.version>
9495
<junit.version>5.11.3</junit.version>
9596
<libphonenumber.version>8.13.55</libphonenumber.version>
@@ -100,8 +101,6 @@
100101
<mockito.version>5.15.2</mockito.version>
101102
<opennlp.version>2.5.3</opennlp.version>
102103
<pdfbox.version>3.0.4</pdfbox.version>
103-
<okhttp.version>4.12.0</okhttp.version>
104-
<retrofit.version>2.11.0</retrofit.version>
105104
<snakeyaml.version>2.3</snakeyaml.version>
106105
</properties>
107106
<build>

0 commit comments

Comments
 (0)
Please sign in to comment.