fix(manager-api-jpa): fix criteria API devportal search issue + add tests
fix(manager-api-jpa): JPA Storage initialisation on WF
test(manager-api): add Jetty `persistence.xml` for tests
chore: ignore dev folder in tools
fix(manager-api): `@BlobResource` interceptor NPE caused by trying to introspect streams
MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/AbstractJpaStorage.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/AbstractJpaStorage.java
@@ -37,6 +37,7 @@ import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@@ -286,7 +287,7 @@ public abstract class AbstractJpaStorage {
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected <T> void applySearchCriteriaToQuery(SearchCriteriaBean criteria, CriteriaBuilder builder,
- CriteriaQuery<?> query, Root<T> from, boolean countOnly) {
+ CriteriaQuery<?> query, From<T, ?> from, boolean countOnly) {
List<SearchCriteriaFilterBean> filters = criteria.getFilters();
if (filters != null && !filters.isEmpty()) {

MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/JpaStorage.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/JpaStorage.java
@@ -784,17 +784,30 @@ public class JpaStorage extends AbstractJpaStorage implements IStorage, IStorage
CriteriaBuilder builder = getActiveEntityManager().getCriteriaBuilder();
CriteriaQuery<ApiBean> criteriaQuery = builder.createQuery(ApiBean.class).distinct(true);
Root<ApiBean> root = criteriaQuery.from(ApiBean.class);
+
super.applySearchCriteriaToQuery(criteria, builder, criteriaQuery , root, false);
- SetJoin<ApiBean, ApiVersionBean> versions = root.join(ApiBean_.apiVersionSet, JoinType.INNER);
- Predicate isExposed = builder.equal(versions.get(ApiVersionBean_.exposeInPortal), true);
- CriteriaQuery<ApiBean> exposedQuery = criteriaQuery.where(isExposed);
+
+ // If no restrictions were applied
+ // TODO(msavy): tidy this up
+ if (criteriaQuery.getRestriction() != null) {
+ SetJoin<ApiBean, ApiVersionBean> apiVersions = root.join(ApiBean_.apiVersionSet, JoinType.INNER);
+ Predicate isExposed = builder.equal(apiVersions.get(ApiVersionBean_.exposeInPortal), true);
+ Predicate exposedAndCustomRestrictions = builder.and(isExposed, criteriaQuery.getRestriction());
+ CriteriaQuery<ApiBean> combinedWhere = criteriaQuery.where(exposedAndCustomRestrictions);
+ } else {
+ SetJoin<ApiBean, ApiVersionBean> apiVersions = root.join(ApiBean_.apiVersionSet, JoinType.INNER);
+ Predicate isExposed = builder.equal(apiVersions.get(ApiVersionBean_.exposeInPortal), true);
+ CriteriaQuery<ApiBean> exposed = criteriaQuery.where(isExposed);
+ }
+
List<ApiBean> results = getActiveEntityManager()
- .createQuery(exposedQuery)
+ .createQuery(criteriaQuery)
.getResultList();
- // TODO(msavy): refactor/add back pagination if we need it
+
return new SearchResultsBean<ApiSummaryBean>()
.setBeans(apiMapper.toSummary(results))
.setTotalSize(results.size());
+
}
// TODO(msavy): optimise this

MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/JpaStorageInitializer.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/JpaStorageInitializer.java
@@ -169,6 +169,7 @@ public class JpaStorageInitializer {
for (String sql : statements) {
LOGGER.info(sql);
jdbi.useHandle(h -> {
+ h.getConnection().setAutoCommit(false);
h.createUpdate(sql).execute();
h.commit();
});

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/interceptors/BlobResourceInterceptorProvider.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/interceptors/BlobResourceInterceptorProvider.java
@@ -16,6 +16,7 @@ import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
@@ -23,6 +24,7 @@ import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
@@ -76,15 +78,7 @@ public class BlobResourceInterceptorProvider implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
try {
- // boolean startedOwnTx = false;
- // if (!getEm().getTransaction().isActive()) {
- // getEm().getTransaction().begin();
- // startedOwnTx = true;
- // }
rewrite(context);
- // if (startedOwnTx) {
- // getEm().getTransaction().commit();
- // }
context.proceed();
} catch (IllegalAccessException e) {
throw new IOException(e);
@@ -149,12 +143,11 @@ public class BlobResourceInterceptorProvider implements WriterInterceptor {
if (isAccessible && !shouldBeIgnored(f, currentNode)) {
Object value = f.get(currentNode);
if (value != null) {
- resolveValue(value).forEach(nodesToSearch::push);
+ resolveValue(value).filter(Objects::nonNull).forEach(nodesToSearch::push);
}
}
}
objectsSeen.add(currentNode);
-
}
}
return results;
@@ -170,7 +163,8 @@ public class BlobResourceInterceptorProvider implements WriterInterceptor {
|| Modifier.isTransient(modifiers)
|| f.isEnumConstant()
|| !f.canAccess(currentNode)
- || f.isAnnotationPresent(JsonIgnore.class);
+ || f.isAnnotationPresent(JsonIgnore.class)
+ || currentNode instanceof StreamingOutput;
}
private boolean isCollectionLike(Object obj) {

MODIFY
manager/test/api/src/main/java/io/apiman/manager/test/junit/ManagerRestTester.java
manager/test/api/src/main/java/io/apiman/manager/test/junit/ManagerRestTester.java
@@ -135,7 +135,7 @@ public class ManagerRestTester extends ParentRunner<TestInfo> {
if (!"true".equals(System.getProperty("apiman.junit.no-server", "false"))) {
startServer();
} else {
- System.out.println("**** APIMan Server suppressed - assuming running tests against a live server. ****");
+ System.out.println("**** Apiman Server suppressed - assuming running tests against a live server. ****");
}
}

ADD
manager/test/api/src/test/resources/META-INF/persistence.xml
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
+
+ <persistence-unit name="apiman-manager-api-jpa" transaction-type="RESOURCE_LOCAL">
+ <non-jta-data-source>java:/apiman/datasources/apiman-manager</non-jta-data-source>
+ <class>io.apiman.manager.api.beans.idm.UserBean</class>
+ <class>io.apiman.manager.api.beans.idm.RoleBean</class>
+ <class>io.apiman.manager.api.beans.idm.RoleMembershipBean</class>
+ <class>io.apiman.manager.api.beans.orgs.OrganizationBean</class>
+ <class>io.apiman.manager.api.beans.clients.ClientBean</class>
+ <class>io.apiman.manager.api.beans.clients.ClientVersionBean</class>
+ <class>io.apiman.manager.api.beans.apis.ApiBean</class>
+ <class>io.apiman.manager.api.beans.apis.ApiVersionBean</class>
+ <class>io.apiman.manager.api.beans.apis.ApiDefinitionBean</class>
+ <class>io.apiman.manager.api.beans.apis.ApiPlanBean</class>
+ <class>io.apiman.manager.api.beans.plans.PlanBean</class>
+ <class>io.apiman.manager.api.beans.plans.PlanVersionBean</class>
+ <class>io.apiman.manager.api.beans.contracts.ContractBean</class>
+ <class>io.apiman.manager.api.beans.policies.PolicyDefinitionBean</class>
+ <class>io.apiman.manager.api.beans.policies.PolicyDefinitionTemplateBean</class>
+ <class>io.apiman.manager.api.beans.policies.PolicyBean</class>
+ <class>io.apiman.manager.api.beans.audit.AuditEntryBean</class>
+ <class>io.apiman.manager.api.beans.gateways.GatewayBean</class>
+ <class>io.apiman.manager.api.beans.plugins.PluginBean</class>
+ <class>io.apiman.manager.api.beans.download.DownloadBean</class>
+ <class>io.apiman.manager.api.beans.system.MetadataBean</class>
+ <class>io.apiman.manager.api.beans.developers.DeveloperBean</class>
+ <class>io.apiman.manager.api.beans.developers.DeveloperMappingBean</class>
+ <class>io.apiman.manager.api.jpa.blobstore.BlobEntity</class>
+ <class>io.apiman.manager.api.beans.notifications.NotificationPreferenceEntity</class>
+ <class>io.apiman.manager.api.beans.notifications.NotificationTypeEntity</class>
+ <class>io.apiman.manager.api.beans.notifications.NotificationEntity</class>
+ <class>io.apiman.manager.api.beans.apis.KeyValueTag</class>
+ <class>io.apiman.manager.api.jpa.model.outbox.OutboxEventEntity</class>
+ <!-- <exclude-unlisted-classes>true</exclude-unlisted-classes>-->
+ <properties>
+ <property name="hibernate.show_sql" value="true" />
+ <property name="hibernate.format_sql" value="true" />
+ <property name="hibernate.id.new_generator_mappings" value="true" />
+ <property name="hibernate.order_updates" value="true"/>
+ <property name="hibernate.connection.autocommit" value="false"/>
+ </properties>
+ </persistence-unit>
+
+</persistence>

MODIFY
manager/test/api/src/test/resources/test-plan-data/devportal/create/000_setup_via_import.resttest
manager/test/api/src/test/resources/test-plan-data/devportal/create/000_setup_via_import.resttest
@@ -23,7 +23,7 @@ Content-Type: application/json
"modifiedBy" : "admin",
"modifiedOn" : "2018-05-09T12:00:00Z",
"type" : "REST",
- "configuration" : "{\"endpoint\":\"${apiman.gateway-endpoint:https://localhost:8443/apiman-gateway-api}\",\"username\":\"${apiman.gateway-endpoint.username:apimanager}\",\"password\":\"${apiman.gateway-endpoint.password:apiman123!}\"}"
+ "configuration": "{\"endpoint\":\"http://localhost:7070/mock-gateway\",\"username\":\"admin\",\"password\":\"$CRYPT::j8rdW76Z5gUI0I+9c8/GrA==\"}"
} ],
"Plugins" : [ ],
"Roles" : [ {

ADD
manager/test/api/src/test/resources/test-plan-data/devportal/devportal/002_search-like-apis-nomatch.resttest
+POST /devportal/apis/search
+Content-Type: application/json
+
+{
+ "filters" : [
+ {
+ "name" : "name",
+ "value" : "blahblahblah",
+ "operator" : "like"
+ }
+ ],
+ "paging" : {
+ "page" : 1,
+ "pageSize" : 20
+ }
+}
+----
+200
+Content-Type: application/json
+
+{
+ "beans": [],
+ "totalSize": 0
+}

ADD
manager/test/api/src/test/resources/test-plan-data/devportal/devportal/003_search-like-apis-match.resttest
+POST /devportal/apis/search
+Content-Type: application/json
+
+{
+ "filters" : [
+ {
+ "name" : "name",
+ "value" : "*API*",
+ "operator" : "like"
+ }
+ ],
+ "paging" : {
+ "page" : 1,
+ "pageSize" : 20
+ }
+}
+----
+200
+Content-Type: application/json
+
+{
+ "beans": [{
+ "organizationId": "test",
+ "organizationName": "test",
+ "id": "Api1",
+ "name": "Api 1",
+ "createdOn": "2021-10-17T22:01:34.000+00:00",
+ "tags": [{
+ "key": "featured",
+ "value": null
+ }]
+ }],
+ "totalSize": 1
+}
\ No newline at end of file

MODIFY
manager/test/api/src/test/resources/test-plans/devportal-testPlan.xml
manager/test/api/src/test/resources/test-plans/devportal-testPlan.xml
<testGroup name="Dev portal functionality">
<test name="Search for all APIs (only APIs with at least 1 exposed version should appear)">test-plan-data/devportal/devportal/000_search_exposed_apis.resttest</test>
<test name="Get all featured APIs">test-plan-data/devportal/devportal/001_get-featured-apis.resttest</test>
+ <test name="Search for 'LIKE' APIs with non-matching filter">test-plan-data/devportal/devportal/002_search-like-apis-nomatch.resttest</test>
+ <test name="Search for 'LIKE' APIs with match">test-plan-data/devportal/devportal/003_search-like-apis-match.resttest</test>
</testGroup>
</testPlan>
\ No newline at end of file

DELETE
manager/test/api/src/test/resources/test-plans/devportal-testplan.xml
-<?xml version="1.0" encoding="UTF-8"?>
-<testPlan xmlns="urn:io.apiman.test:2014:02:testPlan" name="Dev portal tests">
-
- <!-- Create Organizations -->
- <testGroup name="Initial Creates">
- <!-- Setup:
- Plans: Gold Plan, Silver Plan,
- APIs: API 1 (both plans attached); PrivateAPI (both plans attached)
- Api Versions:
- - API1 - 1.0 - Gold = Exposed + featured
- - API1 - 2 - No exposed plans
- - API1 - 3 - Gold + Silver = exposed + featured
- - PrivateAPI - 1.0 - Not exposed in any way. Should not be found on devportal!
- -->
- <test name="Setup via import">test-plan-data/devportal/create/000_setup_via_import.resttest</test>
- </testGroup>
-
- <!-- Test dev portal management -->
- <testGroup name="Dev portal functionality">
- <test name="Search for all APIs (only APIs with at least 1 exposed version should appear)">test-plan-data/devportal/devportal/000_search_exposed_apis.resttest</test>
- <test name="Get all featured APIs">test-plan-data/devportal/devportal/001_get-featured-apis.resttest</test>
- </testGroup>
-</testPlan>
\ No newline at end of file

MODIFY
parent/pom.xml
parent/pom.xml
<!-- Dependency Versions -->
<version.com.fasterxml.jackson>2.12.4</version.com.fasterxml.jackson>
- <version.com.h2database>1.4.199</version.com.h2database> <!-- Unclear why, but 1.4.200 seems to cause some random issues, including inability for DB viewers to parse the data -->
+ <version.com.h2database>1.4.200</version.com.h2database> <!-- Unclear why, but 1.4.200 seems to cause some random issues, including inability for DB viewers to parse the data -->
<version.com.lmax>3.4.4</version.com.lmax>
<version.com.github.seancfoley.ipaddress>5.3.3</version.com.github.seancfoley.ipaddress>
<version.com.squareup.okhttp>2.7.5</version.com.squareup.okhttp>

ADD
tools/.gitignore
+devenv
\ No newline at end of file

fix(manager-api-jpa): fix criteria API devportal search issue + add tests

fix(manager-api-jpa): JPA Storage initialisation on WF

test(manager-api): add Jetty `persistence.xml` for tests

chore: ignore dev folder in tools

fix(manager-api): `@BlobResource` interceptor NPE caused by trying to introspect streams