Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
88baa3e
CF-4258-JaxB Link builder
ksrihari09 Sep 24, 2025
edb7655
CF-4258: review comment changes
ksrihari09 Sep 26, 2025
94f90f6
Implement dynamic domain configuration for Atom Hopper container
ksrihari09 Oct 7, 2025
28be630
CF-4258: review comment changes
ksrihari09 Oct 13, 2025
a37e96f
CF-4258: review comment changes
ksrihari09 Oct 15, 2025
5315d61
CF-4258: review comment changes
ksrihari09 Oct 15, 2025
7a7957e
CF-4258: review comment changes
ksrihari09 Oct 15, 2025
0c61ed3
CF-4258: review comment changes
ksrihari09 Oct 22, 2025
6d22e46
CF-4258: review comment changes
ksrihari09 Oct 22, 2025
aee58f8
CF-4258: review comment changes
ksrihari09 Oct 22, 2025
07a24cc
CF-4258: review comment changes
ksrihari09 Nov 4, 2025
aa068eb
Release 1.2.35: Add health check endpoint and update version
ksrihari09 Nov 10, 2025
337ff32
Fix version consistency: Update all modules to 1.2.35 release version
ksrihari09 Nov 11, 2025
607f9a2
Release 1.2.36: Update version to avoid GitHub Packages conflict
ksrihari09 Nov 11, 2025
0e71669
Release 1.2.37: Final deployment version with health check endpoint
ksrihari09 Nov 11, 2025
79b2ec0
CF-4258: fixes for smoke test failures
ksrihari09 Nov 12, 2025
00a4d23
CF-4258: fixes for smoke test failures
ksrihari09 Nov 12, 2025
52e0bf6
CF-4258: fixes for smoke test failures
ksrihari09 Nov 12, 2025
9a95849
CF-4258: fixes for smoke test failures
ksrihari09 Nov 12, 2025
0f637b7
CF-4258: fixes for smoke test failures
ksrihari09 Nov 12, 2025
2f8471f
CF-4258: fixes for smoke test failures
ksrihari09 Nov 19, 2025
cfcaf41
CF-4258: fixes for smoke test failures
ksrihari09 Nov 20, 2025
374e7e8
CF-4258: fixes for smoke test failures
ksrihari09 Nov 20, 2025
28b3e90
Revert "CF-4258: fixes for smoke test failures"
ksrihari09 Nov 20, 2025
76c4395
CF-4258: fixes for smoke test failures
ksrihari09 Nov 21, 2025
5051954
CF-4258: fixes for smoke test failures
ksrihari09 Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,40 @@ The current status of the Atom Hopper Data Adapters is as follows:

To find out how to install and run Atom Hopper please see the [Atom Hopper Wiki](https://github.com/rackerlabs/atom-hopper/wiki)

###Health Check and Version Endpoints###

Atom Hopper provides multiple endpoints for monitoring and version information:

#### Health Check Endpoint (Recommended for ECS Load Balancers)
* **Endpoint**: `GET /health`
* **Purpose**: Lightweight health check for load balancers and monitoring systems
* **Response**: JSON format with service status
* **Status Code**: 200 OK
* **Example Response**:
```json
{
"service": "atomhopper",
"status": "ok",
"version": "1.14.1-SNAPSHOT"
}
```

#### Build Information Endpoint (Legacy)
* **Endpoint**: `GET /buildinfo`
* **Purpose**: Detailed build information from Maven properties
* **Response**: JSON format with Maven properties
* **Status Code**: 200 OK
* **Example Response**:
```json
{
"version": "1.14.1-SNAPSHOT",
"groupId": "org.atomhopper",
"artifactId": "atomhopper"
}
```

**Note**: The `/health` endpoint is optimized for frequent health checks and does not perform any database operations. Use `/buildinfo` for detailed build information.

###Notes Regarding licensing###

*All files contained with this distribution of Atom Hopper are licenced
Expand Down
4 changes: 2 additions & 2 deletions adapters/dynamoDB_adapters/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
<parent>
<artifactId>parent</artifactId>
<groupId>org.atomhopper</groupId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>


<groupId>org.atomhopper.adapter</groupId>
<artifactId>dynamoDB_adapters</artifactId>
<artifactId>dynamodb-adapters</artifactId>
<packaging>jar</packaging>
<build>
<plugins>
Expand Down
2 changes: 1 addition & 1 deletion adapters/hibernate/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.atomhopper</groupId>
<artifactId>parent</artifactId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
<relativePath>./../../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion adapters/jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.atomhopper</groupId>
<artifactId>parent</artifactId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
<relativePath>./../../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package org.atomhopper.jdbc.adapter;

import org.apache.abdera.Abdera;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Entry;
import org.atomhopper.adapter.request.adapter.GetEntryRequest;
import org.atomhopper.adapter.request.adapter.GetFeedRequest;
import org.atomhopper.response.AdapterResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.SQLException;
import java.util.Collections;

import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;

/**
* Tests error handling scenarios in JdbcFeedSource to ensure proper error responses
* and XML content type handling.
*/
@RunWith(MockitoJUnitRunner.class)
public class JdbcFeedSourceErrorHandlingTest {

@Mock
private JdbcTemplate mockJdbcTemplate;

@Mock
private GetFeedRequest mockGetFeedRequest;

@Mock
private GetEntryRequest mockGetEntryRequest;

private JdbcFeedSource jdbcFeedSource;
private Abdera abdera;

@Before
public void setUp() {
jdbcFeedSource = new JdbcFeedSource();
jdbcFeedSource.setJdbcTemplate(mockJdbcTemplate);
abdera = new Abdera();

// Setup common mock behavior
when(mockGetFeedRequest.getAbdera()).thenReturn(abdera);
when(mockGetFeedRequest.getFeedName()).thenReturn("test-feed");
when(mockGetFeedRequest.getPageSize()).thenReturn("25");
when(mockGetFeedRequest.getSearchQuery()).thenReturn("");

when(mockGetEntryRequest.getAbdera()).thenReturn(abdera);
when(mockGetEntryRequest.getFeedName()).thenReturn("test-feed");
when(mockGetEntryRequest.getEntryId()).thenReturn("test-entry-id");
}

@Test
public void testDatabaseConnectionError() {
// Simulate database connection failure
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenThrow(new DataAccessException("Connection refused") {});

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
fail("Expected DataAccessException to be thrown");
} catch (DataAccessException e) {
assertEquals("Connection refused", e.getMessage());
}
}

@Test
public void testQueryTimeoutError() {
// Simulate query timeout
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenThrow(new QueryTimeoutException("Query timeout", new SQLException()));

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
fail("Expected QueryTimeoutException to be thrown");
} catch (QueryTimeoutException e) {
assertTrue("Should contain query timeout message", e.getMessage().contains("Query timeout"));
}
}

@Test
public void testDataIntegrityViolationError() {
// Simulate data integrity violation
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenThrow(new DataIntegrityViolationException("Data integrity violation"));

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
fail("Expected DataIntegrityViolationException to be thrown");
} catch (DataIntegrityViolationException e) {
assertEquals("Data integrity violation", e.getMessage());
}
}

@Test
public void testInvalidPageSizeHandling() {
// Test with invalid page size
when(mockGetFeedRequest.getPageSize()).thenReturn("invalid");
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenReturn(Collections.emptyList());

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
fail("Expected NumberFormatException to be thrown");
} catch (NumberFormatException e) {
assertTrue("Should contain invalid number format message",
e.getMessage().contains("invalid"));
}
}

@Test
public void testInvalidMarkerAndStartingAtCombination() {
// Test invalid combination of marker and startingAt parameters
when(mockGetFeedRequest.getPageMarker()).thenReturn("some-marker");
when(mockGetFeedRequest.getStartingAt()).thenReturn("2023-01-01T00:00:00Z");

AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);

assertNotNull("Response should not be null", response);
assertEquals("Should return bad request status", HttpStatus.BAD_REQUEST, response.getResponseStatus());
assertTrue("Should contain error message about marker and startingAt",
response.getMessage() != null &&
(response.getMessage().contains("marker") || response.getMessage().contains("startingAt")));
}

@Test
public void testEntryNotFoundHandling() {
// Test entry not found scenario
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenReturn(Collections.emptyList());

AdapterResponse<Entry> response = jdbcFeedSource.getEntry(mockGetEntryRequest);

assertNotNull("Response should not be null", response);
assertEquals("Should return not found status", HttpStatus.NOT_FOUND, response.getResponseStatus());
}

@Test
public void testEmptyFeedHandling() {
// Test empty feed scenario
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenReturn(Collections.emptyList());

AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);

assertNotNull("Response should not be null", response);
assertEquals("Should return success status for empty feed", HttpStatus.OK, response.getResponseStatus());
assertNotNull("Feed should not be null", response.getBody());
}

@Test
public void testSqlExceptionHandling() {
// Test SQL exception during query execution
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenThrow(new DataAccessException("SQL execution error", new SQLException("Table not found")) {});

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
fail("Expected DataAccessException to be thrown");
} catch (DataAccessException e) {
assertTrue("Should contain SQL execution error message", e.getMessage().contains("SQL execution error"));
assertTrue("Should have SQL exception as cause", e.getCause() instanceof SQLException);
}
}

@Test
public void testInvalidSearchQueryHandling() {
// Test with malformed search query
when(mockGetFeedRequest.getSearchQuery()).thenReturn("invalid:search:query:format");
when(mockJdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class)))
.thenReturn(Collections.emptyList());

// Should not throw exception, but handle gracefully
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);

assertNotNull("Response should not be null", response);
assertTrue("Should return success or bad request status",
response.getResponseStatus() == HttpStatus.OK || response.getResponseStatus() == HttpStatus.BAD_REQUEST);
}

@Test
public void testNullJdbcTemplateHandling() {
// Test behavior when JdbcTemplate is null
jdbcFeedSource.setJdbcTemplate(null);

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
fail("Expected NullPointerException to be thrown");
} catch (NullPointerException e) {
// Expected behavior
}
}

@Test
public void testInvalidDateFormatInStartingAt() {
// Test invalid date format in startingAt parameter
when(mockGetFeedRequest.getPageMarker()).thenReturn(null);
when(mockGetFeedRequest.getStartingAt()).thenReturn("invalid-date-format");

try {
AdapterResponse<Feed> response = jdbcFeedSource.getFeed(mockGetFeedRequest);
// The method may handle invalid dates gracefully and return a response
// instead of throwing an exception, which is also valid behavior
assertNotNull("Response should not be null", response);
} catch (IllegalArgumentException e) {
assertTrue("Should contain date parsing error",
e.getMessage().contains("Invalid format") || e.getMessage().contains("parse"));
} catch (Exception e) {
// Other exceptions are also acceptable for invalid date format
assertTrue("Should be a parsing related exception",
e.getMessage().contains("parse") || e.getMessage().contains("format") ||
e.getMessage().contains("date") || e.getMessage().contains("time"));
}
}
}
2 changes: 1 addition & 1 deletion adapters/migration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.atomhopper</groupId>
<artifactId>parent</artifactId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
<relativePath>./../../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion adapters/mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.atomhopper</groupId>
<artifactId>parent</artifactId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
<relativePath>./../../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion adapters/postgres-adapter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.atomhopper</groupId>
<artifactId>parent</artifactId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
<relativePath>./../../pom.xml</relativePath>
</parent>

Expand Down
6 changes: 3 additions & 3 deletions atomhopper/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.atomhopper</groupId>
<artifactId>parent</artifactId>
<version>1.2.35-SNAPSHOT</version>
<version>1.2.45</version>
</parent>

<groupId>org.atomhopper</groupId>
Expand Down Expand Up @@ -64,8 +64,8 @@

<dependency>
<groupId>org.atomhopper.adapter</groupId>
<artifactId>dynamoDB_adapters</artifactId>
<version>1.2.35-SNAPSHOT</version>
<artifactId>dynamodb-adapters</artifactId>
<version>1.2.42</version>
</dependency>

<dependency>
Expand Down
12 changes: 8 additions & 4 deletions atomhopper/src/main/resources/META-INF/atom-server.cfg.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ NOTE: Place this file in the following folder: /etc/atomhopper/atom-server.cfg.x
feed pages in JSON format. The reference attribute must be
a valid bean name that's defined in your application-context.xml.
-->
<provider-filters>
<provider-filter reference="json-filter"/>
<provider-filters>
<provider-filter reference="keystone-auth-filter"/>
<provider-filter reference="tenant-auth-filter"/>
<provider-filter reference="content-validation-filter"/>
<provider-filter reference="category-validation-filter"/>
<!-- <provider-filter reference="json-filter"/> -->
</provider-filters>

<workspace title="Testing Namespace" resource="/namespace/">
<categories-descriptor reference="workspace-categories-descriptor" />

<feed title="Testing Feed" resource="/feed">
<!-- <feed-source reference="hibernate-feed-source" /> -->
<publisher reference="dynamodb-feed-publisher" />
<feed-source reference="jdbc-feed-source" />
<publisher reference="jdbc-feed-publisher" />
</feed>
</workspace>
</atom-hopper-config>
Loading