Java tutorial
/* * 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.api; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; import io.swagger.annotations.Extension; import io.swagger.annotations.ExtensionProperty; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.bucket.Bucket; import org.apache.nifi.registry.event.EventService; import org.apache.nifi.registry.exception.ResourceNotFoundException; import org.apache.nifi.registry.extension.ExtensionBundleVersion; import org.apache.nifi.registry.extension.ExtensionBundleVersionMetadata; import org.apache.nifi.registry.extension.filter.ExtensionBundleVersionFilterParams; import org.apache.nifi.registry.extension.repo.ExtensionRepoArtifact; import org.apache.nifi.registry.extension.repo.ExtensionRepoBucket; import org.apache.nifi.registry.extension.repo.ExtensionRepoGroup; import org.apache.nifi.registry.extension.repo.ExtensionRepoVersion; import org.apache.nifi.registry.extension.repo.ExtensionRepoVersionSummary; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.service.AuthorizationService; import org.apache.nifi.registry.service.RegistryService; import org.apache.nifi.registry.service.extension.ExtensionBundleVersionCoordinate; import org.apache.nifi.registry.web.link.LinkService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Link; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import java.util.ArrayList; import java.util.Set; import java.util.SortedSet; @Component @Path("/extensions/repo") @Api(value = "extension_repository", description = "Interact with extension bundles via the hierarchy of bucket/group/artifact/version.", authorizations = { @Authorization("Authorization") }) public class ExtensionRepositoryResource extends AuthorizableApplicationResource { public static final String CONTENT_DISPOSITION_HEADER = "content-disposition"; private final RegistryService registryService; private final LinkService linkService; @Autowired public ExtensionRepositoryResource(final RegistryService registryService, final LinkService linkService, final AuthorizationService authorizationService, final EventService eventService) { super(authorizationService, eventService); this.registryService = registryService; this.linkService = linkService; } @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets the names of the buckets the current user is authorized for in order to browse the repo by bucket", response = ExtensionRepoBucket.class, responseContainer = "List") @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionRepoBuckets() { final Set<String> authorizedBucketIds = getAuthorizedBucketIds(RequestAction.READ); if (authorizedBucketIds == null || authorizedBucketIds.isEmpty()) { // not authorized for any bucket, return empty list of items return Response.status(Response.Status.OK).entity(new ArrayList<>()).build(); } final SortedSet<ExtensionRepoBucket> repoBuckets = registryService .getExtensionRepoBuckets(authorizedBucketIds); linkService.populateFullLinks(repoBuckets, getBaseUri()); return Response.status(Response.Status.OK).entity(repoBuckets).build(); } @GET @Path("{bucketName}") @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets the groups in the extension repository in the bucket with the given name", response = ExtensionRepoGroup.class, responseContainer = "List", extensions = { @Extension(name = "access-policy", properties = { @ExtensionProperty(name = "action", value = "read"), @ExtensionProperty(name = "resource", value = "/buckets/{bucketId}") }) }) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionRepoGroups( @PathParam("bucketName") @ApiParam("The bucket name") final String bucketName) { final Bucket bucket = registryService.getBucketByName(bucketName); authorizeBucketAccess(RequestAction.READ, bucket.getIdentifier()); final SortedSet<ExtensionRepoGroup> repoGroups = registryService.getExtensionRepoGroups(bucket); linkService.populateFullLinks(repoGroups, getBaseUri()); return Response.status(Response.Status.OK).entity(repoGroups).build(); } @GET @Path("{bucketName}/{groupId}") @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets the artifacts in the extension repository with the given group in the bucket with the given name", response = ExtensionRepoArtifact.class, responseContainer = "List", extensions = { @Extension(name = "access-policy", properties = { @ExtensionProperty(name = "action", value = "read"), @ExtensionProperty(name = "resource", value = "/buckets/{bucketId}") }) }) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionRepoArtifacts( @PathParam("bucketName") @ApiParam("The bucket name") final String bucketName, @PathParam("groupId") @ApiParam("The group id") final String groupId) { final Bucket bucket = registryService.getBucketByName(bucketName); authorizeBucketAccess(RequestAction.READ, bucket.getIdentifier()); final SortedSet<ExtensionRepoArtifact> repoArtifacts = registryService.getExtensionRepoArtifacts(bucket, groupId); linkService.populateFullLinks(repoArtifacts, getBaseUri()); return Response.status(Response.Status.OK).entity(repoArtifacts).build(); } @GET @Path("{bucketName}/{groupId}/{artifactId}") @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets the versions of the artifact in the extension repository specified by the given bucket, group, artifact, and version", response = ExtensionRepoVersionSummary.class, responseContainer = "List", extensions = { @Extension(name = "access-policy", properties = { @ExtensionProperty(name = "action", value = "read"), @ExtensionProperty(name = "resource", value = "/buckets/{bucketId}") }) }) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionBundleVersions( @PathParam("bucketName") @ApiParam("The bucket name") final String bucketName, @PathParam("groupId") @ApiParam("The group identifier") final String groupId, @PathParam("artifactId") @ApiParam("The artifact identifier") final String artifactId) { final Bucket bucket = registryService.getBucketByName(bucketName); authorizeBucketAccess(RequestAction.READ, bucket.getIdentifier()); final SortedSet<ExtensionRepoVersionSummary> repoVersions = registryService.getExtensionRepoVersions(bucket, groupId, artifactId); linkService.populateFullLinks(repoVersions, getBaseUri()); return Response.status(Response.Status.OK).entity(repoVersions).build(); } @GET @Path("{bucketName}/{groupId}/{artifactId}/{version}") @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets the information about the version specified by the given bucket, group, artifact, and version", response = ExtensionRepoVersion.class, extensions = { @Extension(name = "access-policy", properties = { @ExtensionProperty(name = "action", value = "read"), @ExtensionProperty(name = "resource", value = "/buckets/{bucketId}") }) }) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionBundleVersion( @PathParam("bucketName") @ApiParam("The bucket name") final String bucketName, @PathParam("groupId") @ApiParam("The group identifier") final String groupId, @PathParam("artifactId") @ApiParam("The artifact identifier") final String artifactId, @PathParam("version") @ApiParam("The version") final String version) { final Bucket bucket = registryService.getBucketByName(bucketName); authorizeBucketAccess(RequestAction.READ, bucket.getIdentifier()); final ExtensionBundleVersionCoordinate versionCoordinate = new ExtensionBundleVersionCoordinate( bucket.getIdentifier(), groupId, artifactId, version); final ExtensionBundleVersion bundleVersion = registryService.getExtensionBundleVersion(versionCoordinate); final String downloadUri = generateResourceUri("extensions", "repo", bundleVersion.getBucket().getName(), bundleVersion.getExtensionBundle().getGroupId(), bundleVersion.getExtensionBundle().getArtifactId(), bundleVersion.getVersionMetadata().getVersion(), "content"); final String sha256Uri = generateResourceUri("extensions", "repo", bundleVersion.getBucket().getName(), bundleVersion.getExtensionBundle().getGroupId(), bundleVersion.getExtensionBundle().getArtifactId(), bundleVersion.getVersionMetadata().getVersion(), "sha256"); final ExtensionRepoVersion repoVersion = new ExtensionRepoVersion(); repoVersion.setDownloadLink(Link.fromUri(downloadUri).rel("content").build()); repoVersion.setSha256Link(Link.fromUri(sha256Uri).rel("sha256").build()); repoVersion.setSha256Supplied(bundleVersion.getVersionMetadata().getSha256Supplied()); return Response.ok(repoVersion).build(); } @GET @Path("{bucketName}/{groupId}/{artifactId}/{version}/content") @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Gets the binary content of the extension bundle specified by the given bucket, group, artifact, and version", response = byte[].class, extensions = { @Extension(name = "access-policy", properties = { @ExtensionProperty(name = "action", value = "read"), @ExtensionProperty(name = "resource", value = "/buckets/{bucketId}") }) }) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionBundleVersionContent( @PathParam("bucketName") @ApiParam("The bucket name") final String bucketName, @PathParam("groupId") @ApiParam("The group identifier") final String groupId, @PathParam("artifactId") @ApiParam("The artifact identifier") final String artifactId, @PathParam("version") @ApiParam("The version") final String version) { final Bucket bucket = registryService.getBucketByName(bucketName); authorizeBucketAccess(RequestAction.READ, bucket.getIdentifier()); final ExtensionBundleVersionCoordinate versionCoordinate = new ExtensionBundleVersionCoordinate( bucket.getIdentifier(), groupId, artifactId, version); final ExtensionBundleVersion bundleVersion = registryService.getExtensionBundleVersion(versionCoordinate); final StreamingOutput streamingOutput = (output) -> registryService .writeExtensionBundleVersionContent(bundleVersion, output); return Response.ok(streamingOutput) .header(CONTENT_DISPOSITION_HEADER, "attachment; filename = " + bundleVersion.getFilename()) .build(); } @GET @Path("{bucketName}/{groupId}/{artifactId}/{version}/sha256") @Consumes(MediaType.WILDCARD) @Produces(MediaType.TEXT_PLAIN) @ApiOperation(value = "Gets the hex representation of the SHA-256 digest for the binary content of the version of the extension bundle", response = String.class, extensions = { @Extension(name = "access-policy", properties = { @ExtensionProperty(name = "action", value = "read"), @ExtensionProperty(name = "resource", value = "/buckets/{bucketId}") }) }) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionBundleVersionSha256( @PathParam("bucketName") @ApiParam("The bucket name") final String bucketName, @PathParam("groupId") @ApiParam("The group identifier") final String groupId, @PathParam("artifactId") @ApiParam("The artifact identifier") final String artifactId, @PathParam("version") @ApiParam("The version") final String version) { final Bucket bucket = registryService.getBucketByName(bucketName); authorizeBucketAccess(RequestAction.READ, bucket.getIdentifier()); final ExtensionBundleVersionCoordinate versionCoordinate = new ExtensionBundleVersionCoordinate( bucket.getIdentifier(), groupId, artifactId, version); final ExtensionBundleVersion bundleVersion = registryService.getExtensionBundleVersion(versionCoordinate); final String sha256Hex = bundleVersion.getVersionMetadata().getSha256(); return Response.ok(sha256Hex, MediaType.TEXT_PLAIN).build(); } @GET @Path("{groupId}/{artifactId}/{version}/sha256") @Consumes(MediaType.WILDCARD) @Produces(MediaType.TEXT_PLAIN) @ApiOperation(value = "Gets the hex representation of the SHA-256 digest for the binary content of the version of the extension bundle. Since the " + "same group-artifact-version can exist in multiple buckets, this will return the checksum of the first one returned. This will be " + "consistent since the checksum must be the same when existing in multiple buckets.", response = String.class) @ApiResponses({ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400), @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401), @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403), @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getExtensionBundleVersionSha256( @PathParam("groupId") @ApiParam("The group identifier") final String groupId, @PathParam("artifactId") @ApiParam("The artifact identifier") final String artifactId, @PathParam("version") @ApiParam("The version") final String version) { final Set<String> authorizedBucketIds = getAuthorizedBucketIds(RequestAction.READ); if (authorizedBucketIds == null || authorizedBucketIds.isEmpty()) { // not authorized for any bucket, return empty list of items return Response.status(Response.Status.OK).entity(new ArrayList<>()).build(); } // Since we are using the filter params which are optional in the service layer, we need to validate these path params here if (StringUtils.isBlank(groupId)) { throw new IllegalArgumentException("Group id cannot be null or blank"); } if (StringUtils.isBlank(artifactId)) { throw new IllegalArgumentException("Artifact id cannot be null or blank"); } if (StringUtils.isBlank(version)) { throw new IllegalArgumentException("Version cannot be null or blank"); } final ExtensionBundleVersionFilterParams filterParams = ExtensionBundleVersionFilterParams.of(groupId, artifactId, version); final SortedSet<ExtensionBundleVersionMetadata> bundleVersions = registryService .getExtensionBundleVersions(authorizedBucketIds, filterParams); if (bundleVersions.isEmpty()) { throw new ResourceNotFoundException( "An extension bundle version does not exist with the specific group, artifact, and version"); } else { ExtensionBundleVersionMetadata latestVersionMetadata = null; for (ExtensionBundleVersionMetadata versionMetadata : bundleVersions) { if (latestVersionMetadata == null || versionMetadata.getTimestamp() > latestVersionMetadata.getTimestamp()) { latestVersionMetadata = versionMetadata; } } return Response.ok(latestVersionMetadata.getSha256(), MediaType.TEXT_PLAIN).build(); } } }