diff --git a/pom.xml b/pom.xml
index 8c889f8..c2b8ef1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,7 +57,7 @@
io.ipinfo
ipinfo-api
- 3.1.0
+ 3.2.0
compile
diff --git a/src/main/java/io/ipinfo/spring/IPinfoCoreSpring.java b/src/main/java/io/ipinfo/spring/IPinfoCoreSpring.java
new file mode 100644
index 0000000..aa8b836
--- /dev/null
+++ b/src/main/java/io/ipinfo/spring/IPinfoCoreSpring.java
@@ -0,0 +1,111 @@
+package io.ipinfo.spring;
+
+import io.ipinfo.api.IPinfoCore;
+import io.ipinfo.api.model.IPResponseCore;
+import io.ipinfo.spring.strategies.attribute.AttributeStrategy;
+import io.ipinfo.spring.strategies.attribute.SessionAttributeStrategy;
+import io.ipinfo.spring.strategies.interceptor.BotInterceptorStrategy;
+import io.ipinfo.spring.strategies.interceptor.InterceptorStrategy;
+import io.ipinfo.spring.strategies.ip.IPStrategy;
+import io.ipinfo.spring.strategies.ip.SimpleIPStrategy;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+public class IPinfoCoreSpring implements HandlerInterceptor {
+
+ public static final String ATTRIBUTE_KEY =
+ "IPinfoOfficialSparkWrapper.IPResponseCore";
+ private final IPinfoCore ii;
+ private final AttributeStrategy attributeStrategy;
+ private final IPStrategy ipStrategy;
+ private final InterceptorStrategy interceptorStrategy;
+
+ IPinfoCoreSpring(
+ IPinfoCore ii,
+ AttributeStrategy attributeStrategy,
+ IPStrategy ipStrategy,
+ InterceptorStrategy interceptorStrategy
+ ) {
+ this.ii = ii;
+ this.attributeStrategy = attributeStrategy;
+ this.ipStrategy = ipStrategy;
+ this.interceptorStrategy = interceptorStrategy;
+ }
+
+ public static void main(String... args) {
+ System.out.println(
+ "This library is not meant to be run as a standalone jar."
+ );
+ System.exit(0);
+ }
+
+ @Override
+ public boolean preHandle(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler
+ ) throws Exception {
+ if (!interceptorStrategy.shouldRun(request)) {
+ return true;
+ }
+
+ // Don't waste an API call if we already have it.
+ // This should only happen for RequestAttributeStrategy and potentially
+ // other implementations.
+ if (attributeStrategy.hasCoreAttribute(request)) {
+ return true;
+ }
+
+ String ip = ipStrategy.getIPAddress(request);
+ if (ip == null) {
+ return true;
+ }
+
+ IPResponseCore ipResponse = ii.lookupIP(ip);
+ attributeStrategy.storeCoreAttribute(request, ipResponse);
+
+ return true;
+ }
+
+ public static class Builder {
+
+ private IPinfoCore ii = new IPinfoCore.Builder().build();
+ private AttributeStrategy attributeStrategy =
+ new SessionAttributeStrategy();
+ private IPStrategy ipStrategy = new SimpleIPStrategy();
+ private InterceptorStrategy interceptorStrategy =
+ new BotInterceptorStrategy();
+
+ public Builder setIPinfo(IPinfoCore ii) {
+ this.ii = ii;
+ return this;
+ }
+
+ public Builder attributeStrategy(AttributeStrategy attributeStrategy) {
+ this.attributeStrategy = attributeStrategy;
+ return this;
+ }
+
+ public Builder ipStrategy(IPStrategy ipStrategy) {
+ this.ipStrategy = ipStrategy;
+ return this;
+ }
+
+ public Builder interceptorStrategy(
+ InterceptorStrategy interceptorStrategy
+ ) {
+ this.interceptorStrategy = interceptorStrategy;
+ return this;
+ }
+
+ public IPinfoCoreSpring build() {
+ return new IPinfoCoreSpring(
+ ii,
+ attributeStrategy,
+ ipStrategy,
+ interceptorStrategy
+ );
+ }
+ }
+}
diff --git a/src/main/java/io/ipinfo/spring/strategies/attribute/AttributeStrategy.java b/src/main/java/io/ipinfo/spring/strategies/attribute/AttributeStrategy.java
index 24fced4..20c41bc 100644
--- a/src/main/java/io/ipinfo/spring/strategies/attribute/AttributeStrategy.java
+++ b/src/main/java/io/ipinfo/spring/strategies/attribute/AttributeStrategy.java
@@ -1,6 +1,7 @@
package io.ipinfo.spring.strategies.attribute;
import io.ipinfo.api.model.IPResponse;
+import io.ipinfo.api.model.IPResponseCore;
import io.ipinfo.api.model.IPResponseLite;
import jakarta.servlet.http.HttpServletRequest;
@@ -35,4 +36,23 @@ default IPResponseLite getLiteAttribute(HttpServletRequest request) {
default boolean hasLiteAttribute(HttpServletRequest request) {
return getLiteAttribute(request) != null;
}
+
+ default void storeCoreAttribute(
+ HttpServletRequest request,
+ IPResponseCore response
+ ) {
+ throw new UnsupportedOperationException(
+ "This strategy does not support IPResponseCore."
+ );
+ }
+
+ default IPResponseCore getCoreAttribute(HttpServletRequest request) {
+ throw new UnsupportedOperationException(
+ "This strategy does not support IPResponseCore."
+ );
+ }
+
+ default boolean hasCoreAttribute(HttpServletRequest request) {
+ return getCoreAttribute(request) != null;
+ }
}
diff --git a/src/main/java/io/ipinfo/spring/strategies/attribute/RequestAttributeStrategy.java b/src/main/java/io/ipinfo/spring/strategies/attribute/RequestAttributeStrategy.java
index 4143154..f2eb395 100644
--- a/src/main/java/io/ipinfo/spring/strategies/attribute/RequestAttributeStrategy.java
+++ b/src/main/java/io/ipinfo/spring/strategies/attribute/RequestAttributeStrategy.java
@@ -1,7 +1,9 @@
package io.ipinfo.spring.strategies.attribute;
import io.ipinfo.api.model.IPResponse;
+import io.ipinfo.api.model.IPResponseCore;
import io.ipinfo.api.model.IPResponseLite;
+import io.ipinfo.spring.IPinfoCoreSpring;
import io.ipinfo.spring.IPinfoLiteSpring;
import io.ipinfo.spring.IPinfoSpring;
import jakarta.servlet.http.HttpServletRequest;
@@ -35,4 +37,19 @@ public IPResponseLite getLiteAttribute(HttpServletRequest request) {
IPinfoLiteSpring.ATTRIBUTE_KEY
);
}
+
+ @Override
+ public void storeCoreAttribute(
+ HttpServletRequest request,
+ IPResponseCore response
+ ) {
+ request.setAttribute(IPinfoCoreSpring.ATTRIBUTE_KEY, response);
+ }
+
+ @Override
+ public IPResponseCore getCoreAttribute(HttpServletRequest request) {
+ return (IPResponseCore) request.getAttribute(
+ IPinfoCoreSpring.ATTRIBUTE_KEY
+ );
+ }
}
diff --git a/src/main/java/io/ipinfo/spring/strategies/attribute/SessionAttributeStrategy.java b/src/main/java/io/ipinfo/spring/strategies/attribute/SessionAttributeStrategy.java
index b83cac2..d37fe0a 100644
--- a/src/main/java/io/ipinfo/spring/strategies/attribute/SessionAttributeStrategy.java
+++ b/src/main/java/io/ipinfo/spring/strategies/attribute/SessionAttributeStrategy.java
@@ -1,7 +1,9 @@
package io.ipinfo.spring.strategies.attribute;
import io.ipinfo.api.model.IPResponse;
+import io.ipinfo.api.model.IPResponseCore;
import io.ipinfo.api.model.IPResponseLite;
+import io.ipinfo.spring.IPinfoCoreSpring;
import io.ipinfo.spring.IPinfoLiteSpring;
import io.ipinfo.spring.IPinfoSpring;
import jakarta.servlet.http.HttpServletRequest;
@@ -39,4 +41,21 @@ public IPResponseLite getLiteAttribute(HttpServletRequest request) {
.getSession()
.getAttribute(IPinfoLiteSpring.ATTRIBUTE_KEY);
}
+
+ @Override
+ public void storeCoreAttribute(
+ HttpServletRequest request,
+ IPResponseCore response
+ ) {
+ request
+ .getSession()
+ .setAttribute(IPinfoCoreSpring.ATTRIBUTE_KEY, response);
+ }
+
+ @Override
+ public IPResponseCore getCoreAttribute(HttpServletRequest request) {
+ return (IPResponseCore) request
+ .getSession()
+ .getAttribute(IPinfoCoreSpring.ATTRIBUTE_KEY);
+ }
}
diff --git a/src/test/java/io/ipinfo/spring/IPinfoCoreSpringTest.java b/src/test/java/io/ipinfo/spring/IPinfoCoreSpringTest.java
new file mode 100644
index 0000000..da7e934
--- /dev/null
+++ b/src/test/java/io/ipinfo/spring/IPinfoCoreSpringTest.java
@@ -0,0 +1,176 @@
+package io.ipinfo.spring;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import io.ipinfo.api.IPinfoCore;
+import io.ipinfo.api.errors.RateLimitedException;
+import io.ipinfo.api.model.ASN;
+import io.ipinfo.api.model.Geo;
+import io.ipinfo.api.model.IPResponseCore;
+import io.ipinfo.spring.strategies.attribute.AttributeStrategy;
+import io.ipinfo.spring.strategies.interceptor.InterceptorStrategy;
+import io.ipinfo.spring.strategies.ip.IPStrategy;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+@ExtendWith(MockitoExtension.class)
+class IPinfoCoreSpringTest {
+
+ @Mock
+ private IPinfoCore mockIPinfoClient;
+
+ @Mock
+ private AttributeStrategy mockAttributeStrategy;
+
+ @Mock
+ private IPStrategy mockIpStrategy;
+
+ @Mock
+ private InterceptorStrategy mockInterceptorStrategy;
+
+ @InjectMocks
+ private IPinfoCoreSpring ipinfoSpring;
+
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+ private Object handler;
+
+ private IPResponseCore dummyIPResponse;
+
+ @BeforeEach
+ void setUp() {
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ handler = new Object();
+
+ new IPResponseCore(
+ "8.8.8.8",
+ new Geo(
+ "Mountain View",
+ "California",
+ "CA",
+ "United States",
+ "US",
+ "North America",
+ "NA",
+ 37.4056,
+ -122.0775,
+ "America/Los_Angeles",
+ "94043"
+ ),
+ new ASN("AS15169", "Google LLC", "google.com", "", "hosting"),
+ false,
+ true,
+ true,
+ false,
+ false
+ );
+ }
+
+ @Test
+ @DisplayName("should skip processing if interceptorStrategy returns false")
+ void preHandle_shouldSkipIfInterceptorStrategyFalse() throws Exception {
+ when(mockInterceptorStrategy.shouldRun(request)).thenReturn(false);
+
+ boolean result = ipinfoSpring.preHandle(request, response, handler);
+
+ assertTrue(result, "preHandle should return true to continue chain");
+ // Verify that no other strategies were called if shouldRun returned false
+ verify(mockInterceptorStrategy).shouldRun(request);
+ verifyNoInteractions(
+ mockAttributeStrategy,
+ mockIpStrategy,
+ mockIPinfoClient
+ );
+ }
+
+ @Test
+ @DisplayName(
+ "should skip processing if attributeStrategy already has attribute"
+ )
+ void preHandle_shouldSkipIfHasCoreAttribute() throws Exception {
+ when(mockInterceptorStrategy.shouldRun(request)).thenReturn(true);
+ when(mockAttributeStrategy.hasCoreAttribute(request)).thenReturn(true);
+
+ boolean result = ipinfoSpring.preHandle(request, response, handler);
+
+ assertTrue(result, "preHandle should return true to continue chain");
+ verify(mockInterceptorStrategy).shouldRun(request);
+ verify(mockAttributeStrategy).hasCoreAttribute(request);
+ // Verify no IP lookup or storage occurred
+ verifyNoInteractions(mockIpStrategy, mockIPinfoClient);
+ }
+
+ @Test
+ @DisplayName("should skip processing if IPStrategy returns null IP")
+ void preHandle_shouldSkipIfIpIsNull() throws Exception {
+ when(mockInterceptorStrategy.shouldRun(request)).thenReturn(true);
+ when(mockAttributeStrategy.hasCoreAttribute(request)).thenReturn(false);
+ when(mockIpStrategy.getIPAddress(request)).thenReturn(null);
+
+ boolean result = ipinfoSpring.preHandle(request, response, handler);
+
+ assertTrue(result, "preHandle should return true to continue chain");
+ verify(mockInterceptorStrategy).shouldRun(request);
+ verify(mockAttributeStrategy).hasCoreAttribute(request);
+ verify(mockIpStrategy).getIPAddress(request);
+ // Verify no IP lookup or storage occurred
+ verifyNoInteractions(mockIPinfoClient);
+ verify(mockAttributeStrategy, never()).storeCoreAttribute(any(), any());
+ }
+
+ @Test
+ @DisplayName(
+ "should perform IP lookup and store attribute if all conditions met"
+ )
+ void preHandle_shouldProcessAndStore() throws Exception {
+ String testIp = "8.8.8.8";
+ when(mockInterceptorStrategy.shouldRun(request)).thenReturn(true);
+ when(mockAttributeStrategy.hasCoreAttribute(request)).thenReturn(false);
+ when(mockIpStrategy.getIPAddress(request)).thenReturn(testIp);
+ when(mockIPinfoClient.lookupIP(testIp)).thenReturn(dummyIPResponse);
+
+ boolean result = ipinfoSpring.preHandle(request, response, handler);
+
+ assertTrue(result, "preHandle should return true to continue chain");
+ verify(mockInterceptorStrategy).shouldRun(request);
+ verify(mockAttributeStrategy).hasCoreAttribute(request);
+ verify(mockIpStrategy).getIPAddress(request);
+ verify(mockIPinfoClient).lookupIP(testIp);
+ verify(mockAttributeStrategy).storeCoreAttribute(
+ request,
+ dummyIPResponse
+ );
+ }
+
+ @Test
+ @DisplayName("should rethrow RateLimitedException during lookup")
+ void preHandle_shouldRethrowRateLimitedException() throws Exception {
+ String testIp = "invalid.ip";
+ when(mockInterceptorStrategy.shouldRun(request)).thenReturn(true);
+ when(mockAttributeStrategy.hasCoreAttribute(request)).thenReturn(false);
+ when(mockIpStrategy.getIPAddress(request)).thenReturn(testIp);
+ // Simulate a RateLimitedException during lookup
+ when(mockIPinfoClient.lookupIP(testIp)).thenThrow(
+ new RateLimitedException()
+ );
+
+ assertThrows(RateLimitedException.class, () ->
+ ipinfoSpring.preHandle(request, response, handler)
+ );
+
+ verify(mockInterceptorStrategy).shouldRun(request);
+ verify(mockAttributeStrategy).hasCoreAttribute(request);
+ verify(mockIpStrategy).getIPAddress(request);
+ verify(mockIPinfoClient).lookupIP(testIp);
+ verify(mockAttributeStrategy, never()).storeCoreAttribute(any(), any());
+ }
+}