blob: 140794be12d76be069bc484715ef16ace6f0d2a4 [file] [log] [blame]
/*
* 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.event.EventService;
import org.apache.nifi.registry.exception.ResourceNotFoundException;
import org.apache.nifi.registry.extension.bundle.BundleVersionFilterParams;
import org.apache.nifi.registry.extension.bundle.BundleVersionMetadata;
import org.apache.nifi.registry.extension.component.ExtensionMetadata;
import org.apache.nifi.registry.extension.repo.ExtensionRepoArtifact;
import org.apache.nifi.registry.extension.repo.ExtensionRepoBucket;
import org.apache.nifi.registry.extension.repo.ExtensionRepoExtensionMetadata;
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.web.service.ServiceFacade;
import org.apache.nifi.registry.web.service.StreamingContent;
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.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.util.List;
import java.util.SortedSet;
@Component
@Path("/extension-repository")
@Api(
value = "extension repository",
description = "Interact with extension bundles via the hierarchy of bucket/group/artifact/version. ",
authorizations = { @Authorization("Authorization") }
)
public class ExtensionRepoResource extends ApplicationResource {
public static final String CONTENT_DISPOSITION_HEADER = "content-disposition";
@Autowired
public ExtensionRepoResource(final ServiceFacade serviceFacade, final EventService eventService) {
super(serviceFacade, eventService);
}
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo buckets",
notes = "Gets the names of the buckets the current user is authorized for in order to browse the repo by bucket. " + NON_GUARANTEED_ENDPOINT,
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 SortedSet<ExtensionRepoBucket> repoBuckets = serviceFacade.getExtensionRepoBuckets(getBaseUri());
return Response.status(Response.Status.OK).entity(repoBuckets).build();
}
@GET
@Path("{bucketName}")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo groups",
notes = "Gets the groups in the extension repository in the given bucket. " + NON_GUARANTEED_ENDPOINT,
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 SortedSet<ExtensionRepoGroup> repoGroups = serviceFacade.getExtensionRepoGroups(getBaseUri(), bucketName);
return Response.status(Response.Status.OK).entity(repoGroups).build();
}
@GET
@Path("{bucketName}/{groupId}")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo artifacts",
notes = "Gets the artifacts in the extension repository in the given bucket and group. " + NON_GUARANTEED_ENDPOINT,
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 SortedSet<ExtensionRepoArtifact> repoArtifacts = serviceFacade.getExtensionRepoArtifacts(getBaseUri(), bucketName, groupId);
return Response.status(Response.Status.OK).entity(repoArtifacts).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo versions",
notes = "Gets the versions in the extension repository for the given bucket, group, and artifact. " + NON_GUARANTEED_ENDPOINT,
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 getExtensionRepoVersions(
@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 SortedSet<ExtensionRepoVersionSummary> repoVersions = serviceFacade.getExtensionRepoVersions(
getBaseUri(), bucketName, groupId, artifactId);
return Response.status(Response.Status.OK).entity(repoVersions).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo version",
notes = "Gets information about the version in the given bucket, group, and artifact. " + NON_GUARANTEED_ENDPOINT,
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 getExtensionRepoVersion(
@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 ExtensionRepoVersion repoVersion = serviceFacade.getExtensionRepoVersion(
getBaseUri(), bucketName, groupId, artifactId, version);
return Response.ok(repoVersion).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}/extensions")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo extensions",
notes = "Gets information about the extensions in the given bucket, group, artifact, and version. " + NON_GUARANTEED_ENDPOINT,
response = ExtensionMetadata.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 getExtensionRepoVersionExtensions(
@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 List<ExtensionRepoExtensionMetadata> extensionRepoExtensions =
serviceFacade.getExtensionRepoExtensions(
getBaseUri(), bucketName, groupId, artifactId, version);
return Response.ok(extensionRepoExtensions).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}/extensions/{name}")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Get extension repo extension",
notes = "Gets information about the extension with the given name in " +
"the given bucket, group, artifact, and version. " + NON_GUARANTEED_ENDPOINT,
response = org.apache.nifi.registry.extension.component.manifest.Extension.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 getExtensionRepoVersionExtension(
@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,
@PathParam("name")
@ApiParam("The fully qualified name of the extension")
final String name
) {
final org.apache.nifi.registry.extension.component.manifest.Extension extension =
serviceFacade.getExtensionRepoExtension(
getBaseUri(), bucketName, groupId, artifactId, version, name);
return Response.ok(extension).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}/extensions/{name}/docs")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.TEXT_HTML)
@ApiOperation(
value = "Get extension repo extension docs",
notes = "Gets the documentation for the extension with the given name in " +
"the given bucket, group, artifact, and version. " + NON_GUARANTEED_ENDPOINT,
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 getExtensionRepoVersionExtensionDocs(
@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,
@PathParam("name")
@ApiParam("The fully qualified name of the extension")
final String name
) {
final StreamingOutput streamingOutput = serviceFacade.getExtensionRepoExtensionDocs(
getBaseUri(), bucketName, groupId, artifactId, version, name);
return Response.ok(streamingOutput).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}/extensions/{name}/docs/additional-details")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.TEXT_HTML)
@ApiOperation(
value = "Get extension repo extension details",
notes = "Gets the additional details documentation for the extension with the given name in " +
"the given bucket, group, artifact, and version. " + NON_GUARANTEED_ENDPOINT,
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 getExtensionRepoVersionExtensionAdditionalDetailsDocs(
@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,
@PathParam("name")
@ApiParam("The fully qualified name of the extension")
final String name
) {
final StreamingOutput streamingOutput = serviceFacade.getExtensionRepoExtensionAdditionalDocs(
getBaseUri(), bucketName, groupId, artifactId, version, name);
return Response.ok(streamingOutput).build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}/content")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@ApiOperation(
value = "Get extension repo version content",
notes = "Gets the binary content of the bundle with the given bucket, group, artifact, and version. " + NON_GUARANTEED_ENDPOINT,
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 getExtensionRepoVersionContent(
@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 StreamingContent streamingContent = serviceFacade.getExtensionRepoVersionContent(
bucketName, groupId, artifactId, version);
final String filename = streamingContent.getFilename();
final StreamingOutput streamingOutput = streamingContent.getOutput();
return Response.ok(streamingOutput)
.header(CONTENT_DISPOSITION_HEADER,"attachment; filename = " + filename)
.build();
}
@GET
@Path("{bucketName}/{groupId}/{artifactId}/{version}/sha256")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(
value = "Get extension repo version checksum",
notes = "Gets the hex representation of the SHA-256 digest for the binary content of the bundle " +
"with the given bucket, group, artifact, and version." + NON_GUARANTEED_ENDPOINT,
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 getExtensionRepoVersionSha256(
@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 String sha256Hex = serviceFacade.getExtensionRepoVersionSha256(bucketName, groupId, artifactId, version);
return Response.ok(sha256Hex, MediaType.TEXT_PLAIN).build();
}
@GET
@Path("{groupId}/{artifactId}/{version}/sha256")
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(
value = "Get global extension repo version checksum",
notes = "Gets the hex representation of the SHA-256 digest for the binary content with the given bucket, group, artifact, and version. " +
"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. " + NON_GUARANTEED_ENDPOINT,
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 getGlobalExtensionRepoVersionSha256(
@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
) {
// 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 BundleVersionFilterParams filterParams = BundleVersionFilterParams.of(groupId, artifactId, version);
final SortedSet<BundleVersionMetadata> bundleVersions = serviceFacade.getBundleVersions(filterParams);
if (bundleVersions.isEmpty()) {
throw new ResourceNotFoundException("An extension bundle version does not exist with the specific group, artifact, and version");
} else {
BundleVersionMetadata latestVersionMetadata = null;
for (BundleVersionMetadata versionMetadata : bundleVersions) {
if (latestVersionMetadata == null || versionMetadata.getTimestamp() > latestVersionMetadata.getTimestamp()) {
latestVersionMetadata = versionMetadata;
}
}
return Response.ok(latestVersionMetadata.getSha256(), MediaType.TEXT_PLAIN).build();
}
}
}