Repository: nifi-registry Updated Branches: refs/heads/master 88cae4f15 -> a1629c86d
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/a1629c86/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java index 5ad934d..f210a3d 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java @@ -20,8 +20,11 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.flow.VersionedFlow; import org.apache.nifi.registry.flow.VersionedFlowSnapshot; +import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata; +import org.apache.nifi.registry.service.RegistryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +38,8 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.Set; +import java.util.SortedSet; @Path("/flows") @Api( @@ -45,6 +50,12 @@ public class FlowResource { private static final Logger logger = LoggerFactory.getLogger(FlowResource.class); + private final RegistryService registryService; + + public FlowResource(final RegistryService registryService) { + this.registryService = registryService; + } + @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @@ -54,9 +65,8 @@ public class FlowResource { responseContainer = "List" ) public Response getFlows() { - // TODO implement getFlows - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + final Set<VersionedFlow> flows = registryService.getFlows(); + return Response.status(Response.Status.OK).entity(flows).build(); } @GET @@ -72,11 +82,9 @@ public class FlowResource { @ApiResponse(code = 404, message = "The specified resource could not be found."), } ) - public Response getFlow( - @PathParam("flowId") String flowId) { - // TODO implement getFlow - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + public Response getFlow(@PathParam("flowId") final String flowId) { + final VersionedFlow flow = registryService.getFlow(flowId); + return Response.status(Response.Status.OK).entity(flow).build(); } @PUT @@ -92,11 +100,25 @@ public class FlowResource { @ApiResponse(code = 404, message = "The specified resource could not be found."), } ) - public Response updateFlow( - @PathParam("flowId") String flowId) { - // TODO implement updateFlow - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + public Response updateFlow(@PathParam("flowId") final String flowId, final VersionedFlow flow) { + if (StringUtils.isBlank(flowId)) { + throw new IllegalArgumentException("Flow Id cannot be blank"); + } + + if (flow == null) { + throw new IllegalArgumentException("Flow cannot be null"); + } + + if (flow.getIdentifier() != null && !flowId.equals(flow.getIdentifier())) { + throw new IllegalArgumentException("Flow id in path param must match flow id in body"); + } + + if (flow.getIdentifier() == null) { + flow.setIdentifier(flowId); + } + + final VersionedFlow updatedFlow = registryService.updateFlow(flow); + return Response.status(Response.Status.OK).entity(updatedFlow).build(); } @DELETE @@ -112,11 +134,9 @@ public class FlowResource { @ApiResponse(code = 404, message = "The specified resource could not be found."), } ) - public Response deleteFlow( - @PathParam("flowId") String flowId) { - // TODO implement deleteFlow - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + public Response deleteFlow(@PathParam("flowId") final String flowId) { + final VersionedFlow deletedFlow = registryService.deleteFlow(flowId); + return Response.status(Response.Status.OK).entity(deletedFlow).build(); } @POST @@ -128,11 +148,26 @@ public class FlowResource { "The version number is created by the server and a location URI for the created version resource is returned.", response = VersionedFlowSnapshot.class ) - public Response createFlowVersion( - @PathParam("flowId") String flowId) { - // TODO implement createFlowVersion - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + public Response createFlowVersion(@PathParam("flowId") final String flowId, final VersionedFlowSnapshot snapshot) { + if (StringUtils.isBlank(flowId)) { + throw new IllegalArgumentException("Flow Id cannot be blank"); + } + + if (snapshot == null) { + throw new IllegalArgumentException("VersionedFlowSnapshot cannot be null"); + } + + if (snapshot.getSnapshotMetadata() != null && snapshot.getSnapshotMetadata().getFlowIdentifier() != null + && !flowId.equals(snapshot.getSnapshotMetadata().getFlowIdentifier())) { + throw new IllegalArgumentException("Flow id in path param must match flow id in body"); + } + + if (snapshot.getSnapshotMetadata() != null && snapshot.getSnapshotMetadata().getFlowIdentifier() != null) { + snapshot.getSnapshotMetadata().setFlowIdentifier(flowId); + } + + final VersionedFlowSnapshot createdSnapshot = registryService.createFlowSnapshot(snapshot); + return Response.status(Response.Status.OK).entity(createdSnapshot).build(); } @GET @@ -141,9 +176,7 @@ public class FlowResource { @Produces(MediaType.APPLICATION_JSON) @ApiOperation( value = "Get summary of all versions of a flow for a given flow ID.", - response = VersionedFlowSnapshot.class, /* TODO, add a JSON serialization view for VersionedFlowSnapshot - for this endpoint that hides the flowContents property when - this object is returned as part of a collection. */ + response = VersionedFlowSnapshotMetadata.class, responseContainer = "List" ) @ApiResponses( @@ -151,11 +184,9 @@ public class FlowResource { @ApiResponse(code = 404, message = "The specified resource could not be found."), } ) - public Response getFlowVersions( - @PathParam("flowId") String flowId) { - // TODO implement getFlowVersions - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + public Response getFlowVersions(@PathParam("flowId") final String flowId) { + final VersionedFlow flow = registryService.getFlow(flowId); + return Response.status(Response.Status.OK).entity(flow.getSnapshotMetadata()).build(); } @GET @@ -171,11 +202,20 @@ public class FlowResource { @ApiResponse(code = 404, message = "The specified resource could not be found."), } ) - public Response getLatestFlowVersion( - @PathParam("flowId") String flowId) { - // TODO implement getLatestFlowVersion - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + public Response getLatestFlowVersion(@PathParam("flowId") final String flowId) { + final VersionedFlow flow = registryService.getFlow(flowId); + + final SortedSet<VersionedFlowSnapshotMetadata> snapshots = flow.getSnapshotMetadata(); + if (snapshots == null || snapshots.size() == 0) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + final VersionedFlowSnapshotMetadata lastSnapshotMetadata = snapshots.last(); + + final VersionedFlowSnapshot lastSnapshot = registryService.getFlowSnapshot( + lastSnapshotMetadata.getFlowIdentifier(), lastSnapshotMetadata.getVersion()); + + return Response.status(Response.Status.OK).entity(lastSnapshot).build(); } @GET @@ -192,11 +232,10 @@ public class FlowResource { } ) public Response getFlowVersion( - @PathParam("flowId") String flowId, - @PathParam("versionNumber") Integer versionNumber) { - // TODO implement getFlowVersion - logger.error("This API functionality has not yet been implemented."); - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); + @PathParam("flowId") final String flowId, + @PathParam("versionNumber") final Integer versionNumber) { + final VersionedFlowSnapshot snapshot = registryService.getFlowSnapshot(flowId, versionNumber); + return Response.status(Response.Status.OK).entity(snapshot).build(); } } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/a1629c86/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java new file mode 100644 index 0000000..0cc98fc --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.web.mapper; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class IllegalStateExceptionMapper implements ExceptionMapper<IllegalStateException> { + + private static final Logger logger = LoggerFactory.getLogger(IllegalStateExceptionMapper.class); + + @Override + public Response toResponse(IllegalStateException exception) { + // log the error + logger.info(String.format("%s. Returning %s response.", exception, Response.Status.CONFLICT)); + + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, exception); + } + + return Response.status(Response.Status.CONFLICT).entity(exception.getMessage()).type("text/plain").build(); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/a1629c86/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java new file mode 100644 index 0000000..40d3f36 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.web.mapper; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.registry.exception.ResourceNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class ResourceNotFoundExceptionMapper implements ExceptionMapper<ResourceNotFoundException> { + + private static final Logger logger = LoggerFactory.getLogger(ResourceNotFoundExceptionMapper.class); + + @Override + public Response toResponse(ResourceNotFoundException exception) { + // log the error + logger.info(String.format("%s. Returning %s response.", exception, Response.Status.NOT_FOUND)); + + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, exception); + } + + return Response.status(Response.Status.NOT_FOUND).entity(exception.getMessage()).type("text/plain").build(); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/a1629c86/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java new file mode 100644 index 0000000..e24963d --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.web.mapper; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.registry.serialization.SerializationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class SerializationExceptionMapper implements ExceptionMapper<SerializationException> { + + private static final Logger logger = LoggerFactory.getLogger(SerializationExceptionMapper.class); + + @Override + public Response toResponse(SerializationException exception) { + // log the error + logger.info(String.format("%s. Returning %s response.", exception, Response.Status.INTERNAL_SERVER_ERROR)); + + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, exception); + } + + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception.getMessage()).type("text/plain").build(); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/a1629c86/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/TestRestAPI.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/TestRestAPI.java b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/TestRestAPI.java new file mode 100644 index 0000000..fc980f9 --- /dev/null +++ b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/TestRestAPI.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.web; + +import org.apache.nifi.registry.bucket.Bucket; +import org.apache.nifi.registry.flow.VersionedFlow; +import org.apache.nifi.registry.flow.VersionedFlowSnapshot; +import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata; +import org.apache.nifi.registry.flow.VersionedProcessGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +public class TestRestAPI { + + public static final Logger LOGGER = LoggerFactory.getLogger(TestRestAPI.class); + + public static final String REGISTRY_API_URL = "http://localhost:8080/nifi-registry-api"; + public static final String REGISTRY_API_BUCKETS_URL = REGISTRY_API_URL + "/buckets"; + public static final String REGISTRY_API_FLOWS_URL = REGISTRY_API_URL + "/flows"; + + public static void main(String[] args) { + try { + final Client client = ClientBuilder.newClient(); + + // Create a bucket + + final Bucket bucket = new Bucket(); + bucket.setName("First Bucket"); + bucket.setDescription("This is the first bucket."); + + final Bucket createdBucket = client.target(REGISTRY_API_BUCKETS_URL) + .request() + .post( + Entity.entity(bucket, MediaType.APPLICATION_JSON), + Bucket.class + ); + + System.out.println("Created bucket with id: " + createdBucket.getName()); + + // Create a flow + + final VersionedFlow versionedFlow = new VersionedFlow(); + versionedFlow.setName("First Flow"); + versionedFlow.setDescription("This is the first flow."); + + final VersionedFlow createdFlow = client.target(REGISTRY_API_BUCKETS_URL) + .path("/{bucketId}/flows") + .resolveTemplate("bucketId", createdBucket.getIdentifier()) + .request() + .post( + Entity.entity(versionedFlow, MediaType.APPLICATION_JSON), + VersionedFlow.class + ); + + System.out.println("Created flow with id: " + createdFlow.getIdentifier()); + + // Create first snapshot for the flow + + final VersionedFlowSnapshotMetadata snapshotMetadata1 = new VersionedFlowSnapshotMetadata(); + snapshotMetadata1.setBucketIdentifier(createdBucket.getIdentifier()); + snapshotMetadata1.setFlowIdentifier(createdFlow.getIdentifier()); + snapshotMetadata1.setFlowName(createdFlow.getName()); + snapshotMetadata1.setVersion(1); + snapshotMetadata1.setComments("This is snapshot #1."); + + final VersionedProcessGroup snapshotContents1 = new VersionedProcessGroup(); + snapshotContents1.setIdentifier("pg1"); + snapshotContents1.setName("Process Group 1"); + + final VersionedFlowSnapshot snapshot1 = new VersionedFlowSnapshot(); + snapshot1.setSnapshotMetadata(snapshotMetadata1); + snapshot1.setFlowContents(snapshotContents1); + + final VersionedFlowSnapshot createdSnapshot1 = client.target(REGISTRY_API_FLOWS_URL) + .path("/{flowId}/versions") + .resolveTemplate("flowId", createdFlow.getIdentifier()) + .request() + .post( + Entity.entity(snapshot1, MediaType.APPLICATION_JSON_TYPE), + VersionedFlowSnapshot.class + ); + + System.out.println("Created snapshot with version: " + createdSnapshot1.getSnapshotMetadata().getVersion()); + + // Create second snapshot for the flow + + final VersionedFlowSnapshotMetadata snapshotMetadata2 = new VersionedFlowSnapshotMetadata(); + snapshotMetadata2.setBucketIdentifier(createdBucket.getIdentifier()); + snapshotMetadata2.setFlowIdentifier(createdFlow.getIdentifier()); + snapshotMetadata2.setFlowName(createdFlow.getName()); + snapshotMetadata2.setVersion(2); + snapshotMetadata2.setComments("This is snapshot #2."); + + final VersionedProcessGroup snapshotContents2 = new VersionedProcessGroup(); + snapshotContents2.setIdentifier("pg1"); + snapshotContents2.setName("Process Group 1 New Name"); + + final VersionedFlowSnapshot snapshot2 = new VersionedFlowSnapshot(); + snapshot2.setSnapshotMetadata(snapshotMetadata2); + snapshot2.setFlowContents(snapshotContents2); + + final VersionedFlowSnapshot createdSnapshot2 = client.target(REGISTRY_API_FLOWS_URL) + .path("/{flowId}/versions") + .resolveTemplate("flowId", createdFlow.getIdentifier()) + .request() + .post( + Entity.entity(snapshot2, MediaType.APPLICATION_JSON_TYPE), + VersionedFlowSnapshot.class + ); + + System.out.println("Created snapshot with version: " + createdSnapshot2.getSnapshotMetadata().getVersion()); + + // Retrieve the flow by id + + final Response flowResponse = client.target(REGISTRY_API_FLOWS_URL) + .path("/{flowId}") + .resolveTemplate("flowId", createdFlow.getIdentifier()) + .request() + .get(); + + final String flowJson = flowResponse.readEntity(String.class); + System.out.println("Flow: " + flowJson); + } catch (WebApplicationException e) { + LOGGER.error(e.getMessage(), e); + + final Response response = e.getResponse(); + LOGGER.error(response.readEntity(String.class)); + } + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/a1629c86/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 6538337..47cf327 100644 --- a/pom.xml +++ b/pom.xml @@ -244,6 +244,32 @@ <version>${jersey.version}</version> </dependency> <dependency> + <groupId>org.glassfish.jersey.ext</groupId> + <artifactId>jersey-bean-validation</artifactId> + <version>${jersey.version}</version> + <exclusions> + <exclusion> + <groupId>org.glassfish.web</groupId> + <artifactId>javax.el</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>javax.validation</groupId> + <artifactId>validation-api</artifactId> + <version>2.0.0.Final</version> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-validator</artifactId> + <version>6.0.2.Final</version> + </dependency> + <dependency> + <groupId>org.glassfish</groupId> + <artifactId>javax.el</artifactId> + <version>3.0.1-b08</version> + </dependency> + <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> @@ -253,6 +279,17 @@ <artifactId>commons-io</artifactId> <version>2.5</version> </dependency> + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-annotations</artifactId> + <version>1.5.16</version> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>2.7.22</version> + <scope>test</scope> + </dependency> </dependencies> </dependencyManagement>