feat(manager-api): allow update of extended description, expose in portal, endpoint properties
And allow update of various other API Plan attributes.
MODIFY
manager/api/beans/src/main/java/io/apiman/manager/api/beans/apis/ApiPlanBean.java
manager/api/beans/src/main/java/io/apiman/manager/api/beans/apis/ApiPlanBean.java
@@ -107,7 +107,7 @@ public class ApiPlanBean implements Serializable {
@Override
public int hashCode() {
- return Objects.hash(planId, version, exposeInPortal, requiresApproval);
+ return Objects.hash(planId, version);
}
@Override
@@ -119,9 +119,6 @@ public class ApiPlanBean implements Serializable {
return false;
}
ApiPlanBean that = (ApiPlanBean) o;
- return Objects.equals(planId, that.planId)
- && Objects.equals(version, that.version)
- && Objects.equals(exposeInPortal, that.exposeInPortal)
- && Objects.equals(requiresApproval, that.requiresApproval);
+ return Objects.equals(planId, that.planId) && Objects.equals(version, that.version);
}
}

ADD
manager/api/beans/src/main/java/io/apiman/manager/api/beans/apis/dto/ApiVersionMapper.java
+package io.apiman.manager.api.beans.apis.dto;
+
+import io.apiman.manager.api.beans.apis.UpdateApiVersionBean;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Marc Savy {@literal <marc@blackparrotlabs.io>}
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface ApiVersionMapper {
+ ApiVersionMapper INSTANCE = Mappers.getMapper(ApiVersionMapper.class);
+
+ /**
+ * Strip out any fields that are not permitted for change after publication.
+ */
+ @Mappings({
+ @Mapping(source = "endpointProperties", target = "endpointProperties"),
+ @Mapping(source = "extendedDescription", target = "extendedDescription"),
+ @Mapping(source = "exposeInPortal", target = "exposeInPortal"),
+ @Mapping(source = "plans", target = "plans")
+ })
+ UpdateApiVersionBean toPublishedUpdateBean(UpdateApiVersionBean uvb);
+}

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/service/ApiService.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/service/ApiService.java
@@ -21,6 +21,7 @@ import io.apiman.manager.api.beans.apis.NewApiVersionBean;
import io.apiman.manager.api.beans.apis.UpdateApiBean;
import io.apiman.manager.api.beans.apis.UpdateApiVersionBean;
import io.apiman.manager.api.beans.apis.dto.ApiBeanDto;
+import io.apiman.manager.api.beans.apis.dto.ApiVersionMapper;
import io.apiman.manager.api.beans.apis.dto.KeyValueTagDto;
import io.apiman.manager.api.beans.apis.dto.KeyValueTagMapper;
import io.apiman.manager.api.beans.audit.AuditEntryBean;
@@ -591,34 +592,44 @@ public class ApiService implements DataAccessUtilMixin {
return rval;
}
- public SearchResultsBean<AuditEntryBean> getApiVersionActivity(String organizationId,
- String apiId, String version, int page, int pageSize) throws ApiVersionNotFoundException,
- NotAuthorizedException {
-
+ public SearchResultsBean<AuditEntryBean> getApiVersionActivity(String organizationId, String apiId, String version, int page, int pageSize)
+ throws ApiVersionNotFoundException, NotAuthorizedException {
PagingBean paging = PagingBean.create(page, pageSize);
return tryAction(() -> query.auditEntity(organizationId, apiId, version, ApiBean.class, paging));
}
-
- public ApiVersionBean updateApiVersion(String organizationId, String apiId, String version,
- UpdateApiVersionBean bean) throws ApiVersionNotFoundException, NotAuthorizedException {
+ public ApiVersionBean updateApiVersion(String organizationId, String apiId, String version, UpdateApiVersionBean bean)
+ throws ApiVersionNotFoundException, NotAuthorizedException {
ApiVersionBean avb = getApiVersion(organizationId, apiId, version);
return updateApiVersion(avb, bean);
}
- public ApiVersionBean updateApiVersion(ApiVersionBean avb,
- UpdateApiVersionBean bean) throws ApiVersionNotFoundException, NotAuthorizedException {
-
+ public ApiVersionBean updateApiVersion(ApiVersionBean avb, UpdateApiVersionBean bean) throws ApiVersionNotFoundException {
+ if (avb.getStatus() == ApiStatus.Published) {
+ // If published and public API, then allow full modification (caveat emptor).
if (avb.isPublicAPI()) {
- if (avb.getStatus() == ApiStatus.Retired) {
- throw ExceptionFactory.invalidApiStatusException();
+ return updateApiVersionInternal(avb, bean);
+ } else {
+ // TODO refactor with HTTP PATCH?
+ if (bean.getPlans().size() != avb.getPlans().size()) {
+ throw new InvalidParameterException("Can not attach or remove plans from an API Version after it has been published");
+ }
+ if (!avb.getPlans().containsAll(bean.getPlans())) {
+ throw new InvalidParameterException("Plan IDs and versions must not change after publication");
+ }
+ // If published, then mapper copies across only fields that are safe for changed after publication.
+ UpdateApiVersionBean afterPublishUpdate = ApiVersionMapper.INSTANCE.toPublishedUpdateBean(bean);
+ return updateApiVersionInternal(avb, afterPublishUpdate);
}
} else {
- if (avb.getStatus() == ApiStatus.Published || avb.getStatus() == ApiStatus.Retired) {
- throw ExceptionFactory.invalidApiStatusException();
+ return updateApiVersionInternal(avb, bean);
}
}
+ private ApiVersionBean updateApiVersionInternal(ApiVersionBean avb, UpdateApiVersionBean bean) throws ApiVersionNotFoundException {
+ if (avb.getStatus() == ApiStatus.Retired) {
+ throw ExceptionFactory.invalidApiStatusException();
+ }
avb.setModifiedBy(securityContext.getCurrentUser());
avb.setModifiedOn(new Date());
EntityUpdatedData data = new EntityUpdatedData();

MODIFY
manager/api/rest/src/main/java/io/apiman/manager/api/rest/IOrganizationResource.java
manager/api/rest/src/main/java/io/apiman/manager/api/rest/IOrganizationResource.java
@@ -102,6 +102,7 @@ import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@@ -1101,9 +1102,7 @@ public interface IOrganizationResource {
@Produces(MediaType.APPLICATION_JSON)
public ApiVersionBean updateApiVersion(@PathParam("organizationId") String organizationId,
@PathParam("apiId") String apiId, @PathParam("version") String version,
- UpdateApiVersionBean bean) throws ApiVersionNotFoundException, NotAuthorizedException,
- InvalidApiStatusException;
-
+ UpdateApiVersionBean bean) throws ApiVersionNotFoundException, NotAuthorizedException, InvalidApiStatusException;
/**
* Use this endpoint to update the API's definition document. An API
* definition will vary depending on the type of API, and the type of

feat(manager-api): allow update of extended description, expose in portal, endpoint properties

And allow update of various other API Plan attributes.