| /* |
| * 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.service.extension; |
| |
| import org.apache.commons.codec.binary.Hex; |
| import org.apache.commons.codec.digest.DigestUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.lang3.Validate; |
| import org.apache.nifi.registry.bucket.Bucket; |
| import org.apache.nifi.registry.bundle.extract.BundleExtractor; |
| import org.apache.nifi.registry.bundle.model.BundleDetails; |
| import org.apache.nifi.registry.bundle.model.BundleIdentifier; |
| import org.apache.nifi.registry.db.entity.BucketEntity; |
| import org.apache.nifi.registry.db.entity.BundleEntity; |
| import org.apache.nifi.registry.db.entity.BundleVersionDependencyEntity; |
| import org.apache.nifi.registry.db.entity.BundleVersionEntity; |
| import org.apache.nifi.registry.db.entity.ExtensionAdditionalDetailsEntity; |
| import org.apache.nifi.registry.db.entity.ExtensionEntity; |
| import org.apache.nifi.registry.exception.ResourceNotFoundException; |
| import org.apache.nifi.registry.extension.BundleContext; |
| import org.apache.nifi.registry.extension.BundlePersistenceProvider; |
| import org.apache.nifi.registry.extension.bundle.BuildInfo; |
| import org.apache.nifi.registry.extension.bundle.Bundle; |
| import org.apache.nifi.registry.extension.bundle.BundleFilterParams; |
| import org.apache.nifi.registry.extension.bundle.BundleType; |
| import org.apache.nifi.registry.extension.bundle.BundleVersion; |
| import org.apache.nifi.registry.extension.bundle.BundleVersionDependency; |
| import org.apache.nifi.registry.extension.bundle.BundleVersionFilterParams; |
| import org.apache.nifi.registry.extension.bundle.BundleVersionMetadata; |
| import org.apache.nifi.registry.extension.component.ExtensionFilterParams; |
| import org.apache.nifi.registry.extension.component.ExtensionMetadata; |
| import org.apache.nifi.registry.extension.component.TagCount; |
| import org.apache.nifi.registry.extension.component.manifest.Extension; |
| import org.apache.nifi.registry.extension.component.manifest.ProvidedServiceAPI; |
| 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.ExtensionRepoVersionSummary; |
| import org.apache.nifi.registry.properties.NiFiRegistryProperties; |
| import org.apache.nifi.registry.provider.extension.StandardBundleContext; |
| import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; |
| import org.apache.nifi.registry.serialization.Serializer; |
| import org.apache.nifi.registry.service.MetadataService; |
| import org.apache.nifi.registry.service.extension.docs.DocumentationConstants; |
| import org.apache.nifi.registry.service.extension.docs.ExtensionDocWriter; |
| import org.apache.nifi.registry.service.mapper.BucketMappings; |
| import org.apache.nifi.registry.service.mapper.ExtensionMappings; |
| import org.apache.nifi.registry.util.FileUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.stereotype.Service; |
| |
| import javax.validation.ConstraintViolation; |
| import javax.validation.ConstraintViolationException; |
| import javax.validation.Validator; |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.security.DigestInputStream; |
| import java.security.MessageDigest; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.UUID; |
| import java.util.stream.Collectors; |
| |
| @Service |
| public class StandardExtensionService implements ExtensionService { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(StandardExtensionService.class); |
| |
| static final String SNAPSHOT_VERSION_SUFFIX = "SNAPSHOT"; |
| |
| private final Serializer<Extension> extensionSerializer; |
| private final ExtensionDocWriter extensionDocWriter; |
| private final MetadataService metadataService; |
| private final Map<BundleType, BundleExtractor> extractors; |
| private final BundlePersistenceProvider bundlePersistenceProvider; |
| private final Validator validator; |
| private final File extensionsWorkingDir; |
| |
| @Autowired |
| public StandardExtensionService(final Serializer<Extension> extensionSerializer, |
| final ExtensionDocWriter extensionDocWriter, |
| final MetadataService metadataService, |
| final Map<BundleType, BundleExtractor> extractors, |
| final BundlePersistenceProvider bundlePersistenceProvider, |
| final Validator validator, |
| final NiFiRegistryProperties properties) { |
| this.extensionSerializer = extensionSerializer; |
| this.extensionDocWriter = extensionDocWriter; |
| this.metadataService = metadataService; |
| this.extractors = extractors; |
| this.bundlePersistenceProvider = bundlePersistenceProvider; |
| this.validator = validator; |
| this.extensionsWorkingDir = properties.getExtensionsWorkingDirectory(); |
| Validate.notNull(this.extensionSerializer); |
| Validate.notNull(this.metadataService); |
| Validate.notNull(this.extractors); |
| Validate.notNull(this.bundlePersistenceProvider); |
| Validate.notNull(this.validator); |
| Validate.notNull(this.extensionsWorkingDir); |
| } |
| |
| private <T> void validate(T t, String invalidMessage) { |
| final Set<ConstraintViolation<T>> violations = validator.validate(t); |
| if (violations.size() > 0) { |
| throw new ConstraintViolationException(invalidMessage, violations); |
| } |
| } |
| |
| @Override |
| public BundleVersion createBundleVersion(final String bucketIdentifier, final BundleType bundleType, |
| final InputStream inputStream, final String clientSha256) throws IOException { |
| if (StringUtils.isBlank(bucketIdentifier)) { |
| throw new IllegalArgumentException("Bucket identifier cannot be null or blank"); |
| } |
| |
| if (bundleType == null) { |
| throw new IllegalArgumentException("Bundle type cannot be null"); |
| } |
| |
| if (inputStream == null) { |
| throw new IllegalArgumentException("Extension bundle input stream cannot be null"); |
| } |
| |
| if (!extractors.containsKey(bundleType)) { |
| throw new IllegalArgumentException("No metadata extractor is registered for bundle-type: " + bundleType); |
| } |
| |
| // ensure the bucket exists |
| final BucketEntity existingBucket = getBucketEntity(bucketIdentifier); |
| |
| // ensure the extensions directory exists and we can read and write to it |
| FileUtils.ensureDirectoryExistAndCanReadAndWrite(extensionsWorkingDir); |
| |
| final String extensionWorkingFilename = UUID.randomUUID().toString(); |
| final File extensionWorkingFile = new File(extensionsWorkingDir, extensionWorkingFilename); |
| LOGGER.debug("Writing bundle contents to working directory at {}", new Object[]{extensionWorkingFile.getAbsolutePath()}); |
| |
| try { |
| // write the contents of the input stream to a temporary file in the extensions working directory |
| final MessageDigest sha256Digest = DigestUtils.getSha256Digest(); |
| try (final DigestInputStream digestInputStream = new DigestInputStream(inputStream, sha256Digest); |
| final OutputStream out = new FileOutputStream(extensionWorkingFile)) { |
| IOUtils.copy(digestInputStream, out); |
| } |
| |
| // get the hex of the SHA-256 computed by the server and compare to the client provided SHA-256, if one was provided |
| final String sha256Hex = Hex.encodeHexString(sha256Digest.digest()); |
| final boolean sha256Supplied = !StringUtils.isBlank(clientSha256); |
| if (sha256Supplied && !sha256Hex.equalsIgnoreCase(clientSha256)) { |
| LOGGER.error("Client provided SHA-256 of '{}', but server calculated '{}'", new Object[]{clientSha256, sha256Hex}); |
| throw new IllegalStateException("The SHA-256 of the received extension bundle does not match the SHA-256 provided by the client"); |
| } |
| |
| // extract the details of the bundle from the temp file in the working directory |
| final BundleDetails bundleDetails; |
| try (final InputStream in = new FileInputStream(extensionWorkingFile)) { |
| final BundleExtractor extractor = extractors.get(bundleType); |
| bundleDetails = extractor.extract(in); |
| } |
| |
| final BundleIdentifier bundleIdentifier = bundleDetails.getBundleIdentifier(); |
| final BuildInfo buildInfo = bundleDetails.getBuildInfo(); |
| |
| final String groupId = bundleIdentifier.getGroupId(); |
| final String artifactId = bundleIdentifier.getArtifactId(); |
| final String version = bundleIdentifier.getVersion(); |
| |
| final boolean isSnapshotVersion = version.endsWith(SNAPSHOT_VERSION_SUFFIX); |
| final boolean overwriteBundleVersion = isSnapshotVersion || existingBucket.isAllowExtensionBundleRedeploy(); |
| |
| LOGGER.debug("Extracted bundle details - '{}:{}:{}'", new Object[]{groupId, artifactId, version}); |
| |
| // a bundle with the same group, artifact, and version can exist in multiple buckets, but only if it contains the same binary content, or if its a snapshot version |
| // we can determine that by comparing the SHA-256 digest of the incoming bundle against existing bundles with the same group, artifact, version |
| final List<BundleVersionEntity> allExistingVersions = metadataService.getBundleVersionsGlobal(groupId, artifactId, version); |
| for (final BundleVersionEntity existingVersionEntity : allExistingVersions) { |
| if (!existingVersionEntity.getSha256Hex().equals(sha256Hex) && !isSnapshotVersion) { |
| throw new IllegalStateException("Found existing extension bundle with same group, artifact, and version, but different SHA-256 checksums"); |
| } |
| } |
| |
| // get the existing extension bundle entity, or create a new one if one does not exist in the bucket with the group + artifact |
| final long currentTime = System.currentTimeMillis(); |
| final BundleEntity bundleEntity = getOrCreateExtensionBundle(bucketIdentifier, groupId, artifactId, bundleType, currentTime); |
| |
| // check if the version of incoming bundle already exists in the bucket |
| // if it exists and it is a snapshot version or the bucket allows redeploying, then first delete the row in the extension_bundle_version table so we can create a new one |
| // otherwise we throw an exception because we don't allow the same version in the same bucket |
| final BundleVersionEntity existingVersion = metadataService.getBundleVersion(bucketIdentifier, groupId, artifactId, version); |
| if (existingVersion != null) { |
| if (overwriteBundleVersion) { |
| LOGGER.debug("Bundle overwriting allowed, deleting existing version..."); |
| metadataService.deleteBundleVersion(existingVersion); |
| } else { |
| LOGGER.warn("The specified version [{}] already exists for extension bundle [{}].", new Object[]{version, bundleEntity.getId()}); |
| throw new IllegalStateException("The specified version already exists for the given extension bundle"); |
| } |
| } |
| |
| // create the version metadata instance and validate it has all the required fields |
| final String userIdentity = NiFiUserUtils.getNiFiUserIdentity(); |
| final BundleVersionMetadata versionMetadata = new BundleVersionMetadata(); |
| versionMetadata.setId(UUID.randomUUID().toString()); |
| versionMetadata.setBundleId(bundleEntity.getId()); |
| versionMetadata.setBucketId(bucketIdentifier); |
| versionMetadata.setVersion(version); |
| versionMetadata.setTimestamp(currentTime); |
| versionMetadata.setAuthor(userIdentity); |
| versionMetadata.setSha256(sha256Hex); |
| versionMetadata.setSha256Supplied(sha256Supplied); |
| versionMetadata.setContentSize(extensionWorkingFile.length()); |
| versionMetadata.setSystemApiVersion(bundleDetails.getSystemApiVersion()); |
| versionMetadata.setBuildInfo(buildInfo); |
| |
| validate(versionMetadata, "Cannot create extension bundle version"); |
| |
| // create the bundle version in the metadata db |
| final BundleVersionEntity versionEntity = ExtensionMappings.map(versionMetadata); |
| metadataService.createBundleVersion(versionEntity); |
| |
| // create and persist the version dependencies in the metadata db |
| final Set<BundleVersionDependencyEntity> dependencyEntities = getDependencyEntities(versionEntity, bundleDetails); |
| dependencyEntities.forEach(d -> metadataService.createDependency(d)); |
| |
| // create and persist extensions in the metadata db |
| final Set<ExtensionEntity> extensionEntities = getExtensionEntities(versionEntity, bundleDetails); |
| extensionEntities.forEach(e -> metadataService.createExtension(e)); |
| |
| // persist the content of the bundle to the persistence provider |
| persistBundleVersionContent(bundleType, existingBucket, bundleEntity, versionEntity, extensionWorkingFile, overwriteBundleVersion); |
| |
| // get the updated extension bundle so it contains the correct version count |
| final BundleEntity updatedBundle = metadataService.getBundle(bucketIdentifier, groupId, artifactId); |
| |
| // create the full BundleVersion instance to return |
| final BundleVersion bundleVersion = new BundleVersion(); |
| bundleVersion.setVersionMetadata(versionMetadata); |
| bundleVersion.setBundle(ExtensionMappings.map(existingBucket, updatedBundle)); |
| bundleVersion.setBucket(BucketMappings.map(existingBucket)); |
| |
| final Set<BundleVersionDependency> dependencies = new HashSet<>(); |
| dependencyEntities.forEach(d -> dependencies.add(ExtensionMappings.map(d))); |
| bundleVersion.setDependencies(dependencies); |
| |
| LOGGER.debug("Created bundle - '{}:{}:{}'", new Object[]{groupId, artifactId, version}); |
| return bundleVersion; |
| |
| } finally { |
| if (extensionWorkingFile.exists()) { |
| try { |
| extensionWorkingFile.delete(); |
| } catch (Exception e) { |
| LOGGER.warn("Error removing temporary extension bundle file at {}", |
| new Object[]{extensionWorkingFile.getAbsolutePath()}); |
| } |
| } |
| } |
| } |
| |
| private Set<BundleVersionDependencyEntity> getDependencyEntities(final BundleVersionEntity versionEntity, final BundleDetails bundleDetails) { |
| final Set<BundleIdentifier> dependencyCoordinates = bundleDetails.getDependencies(); |
| if (dependencyCoordinates == null) { |
| return Collections.emptySet(); |
| } |
| |
| final Set<BundleVersionDependencyEntity> versionDependencies = new HashSet<>(); |
| |
| for (final BundleIdentifier dependencyCoordinate : dependencyCoordinates) { |
| final BundleVersionDependency versionDependency = new BundleVersionDependency(); |
| versionDependency.setGroupId(dependencyCoordinate.getGroupId()); |
| versionDependency.setArtifactId(dependencyCoordinate.getArtifactId()); |
| versionDependency.setVersion(dependencyCoordinate.getVersion()); |
| validate(versionDependency, "Cannot create extension bundle version dependency"); |
| |
| final BundleVersionDependencyEntity versionDependencyEntity = ExtensionMappings.map(versionDependency); |
| versionDependencyEntity.setId(UUID.randomUUID().toString()); |
| versionDependencyEntity.setExtensionBundleVersionId(versionEntity.getId()); |
| |
| versionDependencies.add(versionDependencyEntity); |
| } |
| |
| return versionDependencies; |
| } |
| |
| private Set<ExtensionEntity> getExtensionEntities(final BundleVersionEntity versionEntity, final BundleDetails bundleDetails) { |
| final Set<Extension> extensions = bundleDetails.getExtensions(); |
| if (extensions == null) { |
| return Collections.emptySet(); |
| } |
| |
| final Set<ExtensionEntity> extensionEntities = new HashSet<>(); |
| final Map<String,String> additionalDetails = bundleDetails.getAdditionalDetails(); |
| |
| for (final Extension extension : extensions) { |
| validate(extension, "Invalid extension due to one or more constraint violations"); |
| |
| // Convert Extension to ExtensionEntity and populate ids |
| final ExtensionEntity extensionEntity = ExtensionMappings.map(extension, extensionSerializer); |
| extensionEntity.setId(UUID.randomUUID().toString()); |
| extensionEntity.setBundleVersionId(versionEntity.getId()); |
| |
| extensionEntity.getRestrictions().forEach(r -> { |
| r.setId(UUID.randomUUID().toString()); |
| r.setExtensionId(extensionEntity.getId()); |
| }); |
| |
| extensionEntity.getProvidedServiceApis().forEach(p -> { |
| p.setId(UUID.randomUUID().toString()); |
| p.setExtensionId(extensionEntity.getId()); |
| }); |
| |
| // Check the additionalDetails map to see if there is an entry, and if so populate it |
| final String additionalDetailsContent = additionalDetails.get(extensionEntity.getName()); |
| if (!StringUtils.isBlank(additionalDetailsContent)) { |
| LOGGER.debug("Found additional details documentation for extension '{}'", new Object[]{extensionEntity.getName()}); |
| extensionEntity.setAdditionalDetails(additionalDetailsContent); |
| } |
| |
| extensionEntities.add(extensionEntity); |
| } |
| |
| return extensionEntities; |
| } |
| |
| |
| private BundleEntity getOrCreateExtensionBundle(final String bucketId, final String groupId, final String artifactId, |
| final BundleType bundleType, final long currentTime) { |
| |
| BundleEntity existingBundleEntity = metadataService.getBundle(bucketId, groupId, artifactId); |
| if (existingBundleEntity == null) { |
| final Bundle bundle = new Bundle(); |
| bundle.setIdentifier(UUID.randomUUID().toString()); |
| bundle.setBucketIdentifier(bucketId); |
| bundle.setName(groupId + ":" + artifactId); |
| bundle.setGroupId(groupId); |
| bundle.setArtifactId(artifactId); |
| bundle.setBundleType(bundleType); |
| bundle.setCreatedTimestamp(currentTime); |
| bundle.setModifiedTimestamp(currentTime); |
| |
| validate(bundle, "Cannot create extension bundle"); |
| existingBundleEntity = metadataService.createBundle(ExtensionMappings.map(bundle)); |
| } else { |
| if (bundleType != existingBundleEntity.getBundleType()) { |
| throw new IllegalStateException("A bundle already exists with the same group id and artifact id, but a different bundle type"); |
| } |
| } |
| |
| return existingBundleEntity; |
| } |
| |
| private void persistBundleVersionContent(final BundleType bundleType, final BucketEntity bucket, final BundleEntity bundle, |
| final BundleVersionEntity bundleVersion, final File extensionWorkingFile, |
| final boolean overwriteBundleVersion) throws IOException { |
| final BundleContext context = new StandardBundleContext.Builder() |
| .bundleType(getProviderBundleType(bundleType)) |
| .bucketId(bucket.getId()) |
| .bucketName(bucket.getName()) |
| .bundleId(bundle.getId()) |
| .bundleGroupId(bundle.getGroupId()) |
| .bundleArtifactId(bundle.getArtifactId()) |
| .bundleVersion(bundleVersion.getVersion()) |
| .author(bundleVersion.getCreatedBy()) |
| .timestamp(bundleVersion.getCreated().getTime()) |
| .build(); |
| |
| try (final InputStream in = new FileInputStream(extensionWorkingFile); |
| final InputStream bufIn = new BufferedInputStream(in)) { |
| bundlePersistenceProvider.saveBundleVersion(context, bufIn, overwriteBundleVersion); |
| LOGGER.debug("Bundle saved to persistence provider - '{}' - '{}' - '{}'", |
| new Object[]{context.getBundleGroupId(), context.getBundleArtifactId(), context.getBundleVersion()}); |
| } |
| } |
| |
| private BundleContext.BundleType getProviderBundleType(final BundleType bundleType) { |
| switch (bundleType) { |
| case NIFI_NAR: |
| return BundleContext.BundleType.NIFI_NAR; |
| case MINIFI_CPP: |
| return BundleContext.BundleType.MINIFI_CPP; |
| default: |
| throw new IllegalArgumentException("Unknown bundle type: " + bundleType.toString()); |
| } |
| } |
| |
| @Override |
| public List<Bundle> getBundles(final Set<String> bucketIdentifiers, final BundleFilterParams filterParams) { |
| if (bucketIdentifiers == null) { |
| throw new IllegalArgumentException("Bucket identifiers cannot be null"); |
| } |
| |
| final List<BundleEntity> bundleEntities = metadataService.getBundles(bucketIdentifiers, |
| filterParams == null ? BundleFilterParams.empty() : filterParams); |
| return bundleEntities.stream().map(b -> ExtensionMappings.map(null, b)).collect(Collectors.toList()); |
| } |
| |
| @Override |
| public List<Bundle> getBundlesByBucket(final String bucketIdentifier) { |
| if (StringUtils.isBlank(bucketIdentifier)) { |
| throw new IllegalArgumentException("Bucket identifier cannot be null or blank"); |
| } |
| final BucketEntity existingBucket = getBucketEntity(bucketIdentifier); |
| |
| final List<BundleEntity> bundleEntities = metadataService.getBundlesByBucket(bucketIdentifier); |
| return bundleEntities.stream().map(b -> ExtensionMappings.map(existingBucket, b)).collect(Collectors.toList()); |
| } |
| |
| @Override |
| public Bundle getBundle(final String bundleIdentifier) { |
| if (StringUtils.isBlank(bundleIdentifier)) { |
| throw new IllegalArgumentException("Bundle identifier cannot be null or blank"); |
| } |
| |
| final BundleEntity existingBundle = getBundleEntity(bundleIdentifier); |
| final BucketEntity existingBucket = getBucketEntity(existingBundle.getBucketId()); |
| return ExtensionMappings.map(existingBucket, existingBundle); |
| } |
| |
| @Override |
| public Bundle deleteBundle(final Bundle bundle) { |
| if (bundle == null) { |
| throw new IllegalArgumentException("Extension bundle cannot be null"); |
| } |
| |
| // delete the bundle from the database |
| metadataService.deleteBundle(bundle.getIdentifier()); |
| |
| // delete all content associated with the bundle in the persistence provider |
| bundlePersistenceProvider.deleteAllBundleVersions( |
| bundle.getBucketIdentifier(), |
| bundle.getBucketName(), |
| bundle.getGroupId(), |
| bundle.getArtifactId()); |
| |
| return bundle; |
| } |
| |
| // ---- BundleVersion methods ----- |
| |
| @Override |
| public SortedSet<BundleVersionMetadata> getBundleVersions(final Set<String> bucketIdentifiers, |
| final BundleVersionFilterParams filterParams) { |
| if (bucketIdentifiers == null) { |
| throw new IllegalArgumentException("Bucket identifiers cannot be null"); |
| } |
| |
| final SortedSet<BundleVersionMetadata> sortedVersions = new TreeSet<>( |
| Comparator.comparing(BundleVersionMetadata::getBundleId) |
| .thenComparing(BundleVersionMetadata::getVersion) |
| ); |
| |
| final List<BundleVersionEntity> bundleVersionEntities = metadataService.getBundleVersions(bucketIdentifiers, |
| filterParams == null ? BundleVersionFilterParams.empty() : filterParams); |
| if (bundleVersionEntities != null) { |
| bundleVersionEntities.forEach(bv -> sortedVersions.add(ExtensionMappings.map(bv))); |
| } |
| return sortedVersions; |
| } |
| |
| @Override |
| public SortedSet<BundleVersionMetadata> getBundleVersions(final String bundleIdentifier) { |
| if (StringUtils.isBlank(bundleIdentifier)) { |
| throw new IllegalArgumentException("Extension bundle identifier cannot be null or blank"); |
| } |
| |
| // ensure the bundle exists |
| final BundleEntity existingBundle = getBundleEntity(bundleIdentifier); |
| |
| return getExtensionBundleVersionsSet(existingBundle); |
| } |
| |
| private SortedSet<BundleVersionMetadata> getExtensionBundleVersionsSet(final BundleEntity existingBundle) { |
| final SortedSet<BundleVersionMetadata> sortedVersions = new TreeSet<>(Collections.reverseOrder()); |
| |
| final List<BundleVersionEntity> existingVersions = metadataService.getBundleVersions(existingBundle.getId()); |
| if (existingVersions != null) { |
| existingVersions.stream().forEach(s -> sortedVersions.add(ExtensionMappings.map(s))); |
| } |
| return sortedVersions; |
| } |
| |
| @Override |
| public BundleVersion getBundleVersion(final String bucketId, final String bundleId, final String version) { |
| if (StringUtils.isBlank(bucketId)) { |
| throw new IllegalArgumentException("Bucket id cannot be null or blank"); |
| } |
| |
| if (StringUtils.isBlank(bundleId)) { |
| throw new IllegalArgumentException("Bundle id cannot be null or blank"); |
| } |
| |
| if (StringUtils.isBlank(version)) { |
| throw new IllegalArgumentException("Version cannot be null or blank"); |
| } |
| |
| // ensure the bucket exists |
| final BucketEntity existingBucket = getBucketEntity(bucketId); |
| |
| // ensure the bundle exists |
| final BundleEntity existingBundle = getBundleEntity(bundleId); |
| |
| if (!existingBucket.getId().equals(existingBundle.getBucketId())) { |
| throw new IllegalStateException("The requested bundle is not located in the given bucket"); |
| } |
| |
| // retrieve the version of the bundle... |
| final BundleVersionEntity existingVersion = metadataService.getBundleVersion(bundleId, version); |
| if (existingVersion == null) { |
| LOGGER.warn("The specified version [{}] does not exist for extension bundle [{}].", new Object[]{version, bundleId}); |
| throw new ResourceNotFoundException("The specified extension bundle version does not exist."); |
| } |
| return getBundleVersion(existingBucket, existingBundle, existingVersion); |
| |
| } |
| |
| @Override |
| public BundleVersion getBundleVersion(final String bucketId, final String groupId, final String artifactId, final String version) { |
| if (StringUtils.isBlank(bucketId)) { |
| throw new IllegalArgumentException("Bucket id cannot be null or blank"); |
| } |
| |
| 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"); |
| } |
| |
| // ensure the bucket exists |
| final BucketEntity existingBucket = getBucketEntity(bucketId); |
| |
| // ensure the bundle exists |
| final BundleEntity existingBundle = metadataService.getBundle(bucketId, groupId, artifactId); |
| if (existingBundle == null) { |
| LOGGER.warn("The specified extension bundle [{}-{}-{}] does not exist.", new Object[]{bucketId, groupId, artifactId}); |
| throw new ResourceNotFoundException("The specified extension bundle does not exist in this bucket."); |
| } |
| |
| //ensure the version of the bundle exists |
| final BundleVersionEntity existingVersion = metadataService.getBundleVersion(bucketId, groupId, artifactId, version); |
| if (existingVersion == null) { |
| LOGGER.warn("The specified extension bundle version [{}-{}-{}-{}] does not exist.", new Object[]{bucketId, groupId, artifactId, version}); |
| throw new ResourceNotFoundException("The specified extension bundle version does not exist in this bucket."); |
| } |
| |
| // get the dependencies for the bundle version |
| return getBundleVersion(existingBucket, existingBundle, existingVersion); |
| } |
| |
| private BundleVersion getBundleVersion(final BucketEntity existingBucket, final BundleEntity existingBundle, final BundleVersionEntity existingVersion) { |
| // get the dependencies for the bundle version |
| final List<BundleVersionDependencyEntity> existingVersionDependencies = metadataService |
| .getDependenciesForBundleVersion(existingVersion.getId()); |
| |
| // convert the dependency db entities |
| final Set<BundleVersionDependency> dependencies = existingVersionDependencies.stream() |
| .map(d -> ExtensionMappings.map(d)) |
| .collect(Collectors.toSet()); |
| |
| // create the full BundleVersion instance to return |
| final BundleVersion bundleVersion = new BundleVersion(); |
| bundleVersion.setVersionMetadata(ExtensionMappings.map(existingVersion)); |
| bundleVersion.setBundle(ExtensionMappings.map(existingBucket, existingBundle)); |
| bundleVersion.setBucket(BucketMappings.map(existingBucket)); |
| bundleVersion.setDependencies(dependencies); |
| return bundleVersion; |
| } |
| |
| @Override |
| public void writeBundleVersionContent(final BundleVersion bundleVersion, final OutputStream out) { |
| // get the content from the persistence provider and write it to the output stream |
| final BundleContext context = getExtensionBundleContext(bundleVersion); |
| bundlePersistenceProvider.getBundleVersion(context, out); |
| } |
| |
| @Override |
| public BundleVersion deleteBundleVersion(final BundleVersion bundleVersion) { |
| if (bundleVersion == null) { |
| throw new IllegalArgumentException("Extension bundle version cannot be null"); |
| } |
| |
| // delete from the metadata db |
| final String extensionBundleVersionId = bundleVersion.getVersionMetadata().getId(); |
| metadataService.deleteBundleVersion(extensionBundleVersionId); |
| |
| // delete content associated with the bundle version in the persistence provider |
| final BundleContext context = new StandardBundleContext.Builder() |
| .bundleType(getProviderBundleType(bundleVersion.getBundle().getBundleType())) |
| .bucketId(bundleVersion.getBucket().getIdentifier()) |
| .bucketName(bundleVersion.getBucket().getName()) |
| .bundleId(bundleVersion.getBundle().getIdentifier()) |
| .bundleGroupId(bundleVersion.getBundle().getGroupId()) |
| .bundleArtifactId(bundleVersion.getBundle().getArtifactId()) |
| .bundleVersion(bundleVersion.getVersionMetadata().getVersion()) |
| .author(bundleVersion.getVersionMetadata().getAuthor()) |
| .timestamp(bundleVersion.getVersionMetadata().getTimestamp()) |
| .build(); |
| |
| bundlePersistenceProvider.deleteBundleVersion(context); |
| |
| return bundleVersion; |
| } |
| |
| // ------ Extension Methods ---- |
| |
| @Override |
| public SortedSet<ExtensionMetadata> getExtensionMetadata(final Set<String> bucketIdentifiers, final ExtensionFilterParams filterParams) { |
| if (bucketIdentifiers == null) { |
| throw new IllegalArgumentException("Bucket identifiers cannot be null"); |
| } |
| |
| // retrieve the extension entities |
| final List<ExtensionEntity> extensionEntities = metadataService.getExtensions(bucketIdentifiers, filterParams); |
| return getExtensionMetadata(extensionEntities); |
| } |
| |
| @Override |
| public SortedSet<ExtensionMetadata> getExtensionMetadata(final Set<String> bucketIdentifiers, final ProvidedServiceAPI serviceAPI) { |
| if (bucketIdentifiers == null) { |
| throw new IllegalArgumentException("Bucket identifiers cannot be null"); |
| } |
| |
| if (serviceAPI == null |
| || StringUtils.isBlank(serviceAPI.getClassName()) |
| || StringUtils.isBlank(serviceAPI.getGroupId()) |
| || StringUtils.isBlank(serviceAPI.getArtifactId()) |
| || StringUtils.isBlank(serviceAPI.getVersion())) { |
| throw new IllegalArgumentException("Provided service API must be specified with a class, group, artifact, and version"); |
| } |
| |
| // retrieve the extension entities |
| final List<ExtensionEntity> extensionEntities = metadataService.getExtensionsByProvidedServiceApi(bucketIdentifiers, serviceAPI); |
| return getExtensionMetadata(extensionEntities); |
| } |
| |
| private SortedSet<ExtensionMetadata> getExtensionMetadata(List<ExtensionEntity> extensionEntities) { |
| // map to extension metadata and sort by extension name |
| final SortedSet<ExtensionMetadata> extensions = new TreeSet<>(); |
| extensionEntities.forEach(e -> { |
| final ExtensionMetadata metadata = ExtensionMappings.mapToMetadata(e, extensionSerializer); |
| extensions.add(metadata); |
| }); |
| return extensions; |
| } |
| |
| @Override |
| public SortedSet<ExtensionMetadata> getExtensionMetadata(final BundleVersion bundleVersion) { |
| if (bundleVersion == null) { |
| throw new IllegalArgumentException("Extension bundle version cannot be null"); |
| } |
| |
| // ensure the bundle version exists |
| final BundleVersionEntity existingBundleVersion = metadataService.getBundleVersion( |
| bundleVersion.getVersionMetadata().getBucketId(), |
| bundleVersion.getBundle().getGroupId(), |
| bundleVersion.getBundle().getArtifactId(), |
| bundleVersion.getVersionMetadata().getVersion()); |
| |
| if (existingBundleVersion == null) { |
| LOGGER.warn("The specified extension bundle version does not exist for [{}] - [{}] - [{}] - [{}]", |
| new Object[]{ |
| bundleVersion.getVersionMetadata().getBucketId(), |
| bundleVersion.getBundle().getGroupId(), |
| bundleVersion.getBundle().getArtifactId(), |
| bundleVersion.getVersionMetadata().getVersion()}); |
| throw new ResourceNotFoundException("The specified extension bundle version does not exist."); |
| } |
| |
| // retrieve the extension entities |
| final List<ExtensionEntity> extensionEntities = metadataService.getExtensionsByBundleVersionId(existingBundleVersion.getId()); |
| |
| // map to extension and sort by extension name |
| final SortedSet<ExtensionMetadata> extensions = new TreeSet<>(); |
| extensionEntities.forEach(e -> extensions.add(ExtensionMappings.mapToMetadata(e, extensionSerializer))); |
| return extensions; |
| } |
| |
| @Override |
| public Extension getExtension(final BundleVersion bundleVersion, final String name) { |
| if (bundleVersion == null) { |
| throw new IllegalArgumentException("Bundle version cannot be null"); |
| } |
| |
| if (bundleVersion.getVersionMetadata() == null || StringUtils.isBlank(bundleVersion.getVersionMetadata().getId())) { |
| throw new IllegalArgumentException("Bundle version must contain a version metadata with a bundle version id"); |
| } |
| |
| if (StringUtils.isBlank(name)) { |
| throw new IllegalArgumentException("Extension name cannot be null or blank"); |
| } |
| |
| final ExtensionEntity entity = metadataService.getExtensionByName(bundleVersion.getVersionMetadata().getId(), name); |
| if (entity == null) { |
| LOGGER.warn("The specified extension [{}] does not exist in the specified bundle version [{}].", |
| new Object[]{name, bundleVersion.getVersionMetadata().getId()}); |
| throw new ResourceNotFoundException("The specified extension does not exist in this registry."); |
| } |
| |
| return ExtensionMappings.map(entity, extensionSerializer); |
| } |
| |
| @Override |
| public void writeExtensionDocs(final BundleVersion bundleVersion, final String name, final OutputStream outputStream) |
| throws IOException { |
| if (bundleVersion == null) { |
| throw new IllegalArgumentException("Bundle version cannot be null"); |
| } |
| |
| if (bundleVersion.getVersionMetadata() == null || StringUtils.isBlank(bundleVersion.getVersionMetadata().getId())) { |
| throw new IllegalArgumentException("Bundle version must contain a version metadata with a bundle version id"); |
| } |
| |
| if (StringUtils.isBlank(name)) { |
| throw new IllegalArgumentException("Extension name cannot be null or blank"); |
| } |
| |
| if (outputStream == null) { |
| throw new IllegalArgumentException("Output stream cannot be null"); |
| } |
| |
| final ExtensionEntity entity = metadataService.getExtensionByName(bundleVersion.getVersionMetadata().getId(), name); |
| if (entity == null) { |
| LOGGER.warn("The specified extension [{}] does not exist in the specified bundle version [{}].", |
| new Object[]{name, bundleVersion.getVersionMetadata().getId()}); |
| throw new ResourceNotFoundException("The specified extension does not exist in this registry."); |
| } |
| |
| final ExtensionMetadata extensionMetadata = ExtensionMappings.mapToMetadata(entity, extensionSerializer); |
| final Extension extension = ExtensionMappings.map(entity, extensionSerializer); |
| extensionDocWriter.write(extensionMetadata, extension, outputStream); |
| } |
| |
| @Override |
| public void writeAdditionalDetailsDocs(final BundleVersion bundleVersion, final String name, final OutputStream outputStream) throws IOException { |
| if (bundleVersion == null) { |
| throw new IllegalArgumentException("Bundle version cannot be null"); |
| } |
| |
| if (bundleVersion.getVersionMetadata() == null || StringUtils.isBlank(bundleVersion.getVersionMetadata().getId())) { |
| throw new IllegalArgumentException("Bundle version must contain a version metadata with a bundle version id"); |
| } |
| |
| if (StringUtils.isBlank(name)) { |
| throw new IllegalArgumentException("Extension name cannot be null or blank"); |
| } |
| |
| if (outputStream == null) { |
| throw new IllegalArgumentException("Output stream cannot be null"); |
| } |
| |
| final ExtensionAdditionalDetailsEntity additionalDetailsEntity = metadataService.getExtensionAdditionalDetails( |
| bundleVersion.getVersionMetadata().getId(), name); |
| |
| if (additionalDetailsEntity == null) { |
| LOGGER.warn("The specified extension [{}] does not exist in the specified bundle version [{}].", |
| new Object[]{name, bundleVersion.getVersionMetadata().getId()}); |
| throw new ResourceNotFoundException("The specified extension does not exist in this registry."); |
| } |
| |
| if (!additionalDetailsEntity.getAdditionalDetails().isPresent()) { |
| LOGGER.warn("The specified extension [{}] does not have additional details in the specified bundle version [{}].", |
| new Object[]{name, bundleVersion.getVersionMetadata().getId()}); |
| throw new IllegalStateException("The specified extension does not have additional details."); |
| } |
| |
| final String additionalDetailsContent = additionalDetailsEntity.getAdditionalDetails().get(); |
| |
| // The additional details content may have come from NiFi which has a different path to the css so we need to fix the location |
| final String componentUsageCssRef = DocumentationConstants.CSS_PATH + "component-usage.css"; |
| final String updatedContent = additionalDetailsContent.replace("../../../../../css/component-usage.css", componentUsageCssRef); |
| |
| IOUtils.write(updatedContent, outputStream, StandardCharsets.UTF_8); |
| } |
| |
| @Override |
| public SortedSet<TagCount> getExtensionTags() { |
| final SortedSet<TagCount> tagCounts = new TreeSet<>(); |
| metadataService.getAllExtensionTags().forEach(tc -> tagCounts.add(ExtensionMappings.map(tc))); |
| return tagCounts; |
| } |
| |
| // ------ Extension Repository Methods ------- |
| |
| @Override |
| public SortedSet<ExtensionRepoBucket> getExtensionRepoBuckets(final Set<String> bucketIds) { |
| if (bucketIds == null) { |
| throw new IllegalArgumentException("Bucket ids cannot be null"); |
| } |
| |
| if (bucketIds.isEmpty()) { |
| return new TreeSet<>(); |
| } |
| |
| final SortedSet<ExtensionRepoBucket> repoBuckets = new TreeSet<>(); |
| |
| final List<BucketEntity> buckets = metadataService.getBuckets(bucketIds); |
| buckets.forEach(b -> { |
| final ExtensionRepoBucket repoBucket = new ExtensionRepoBucket(); |
| repoBucket.setBucketName(b.getName()); |
| repoBuckets.add(repoBucket); |
| }); |
| |
| return repoBuckets; |
| } |
| |
| @Override |
| public SortedSet<ExtensionRepoGroup> getExtensionRepoGroups(final Bucket bucket) { |
| if (bucket == null) { |
| throw new IllegalArgumentException("Bucket cannot be null"); |
| } |
| |
| final SortedSet<ExtensionRepoGroup> repoGroups = new TreeSet<>(); |
| |
| final List<BundleEntity> bundleEntities = metadataService.getBundlesByBucket(bucket.getIdentifier()); |
| bundleEntities.forEach(b -> { |
| final ExtensionRepoGroup repoGroup = new ExtensionRepoGroup(); |
| repoGroup.setBucketName(bucket.getName()); |
| repoGroup.setGroupId(b.getGroupId()); |
| repoGroups.add(repoGroup); |
| }); |
| |
| return repoGroups; |
| } |
| |
| @Override |
| public SortedSet<ExtensionRepoArtifact> getExtensionRepoArtifacts(final Bucket bucket, final String groupId) { |
| if (bucket == null) { |
| throw new IllegalArgumentException("Bucket cannot be null"); |
| } |
| |
| if (StringUtils.isBlank(groupId)) { |
| throw new IllegalArgumentException("Group id cannot be null or blank"); |
| } |
| |
| final SortedSet<ExtensionRepoArtifact> repoArtifacts = new TreeSet<>(); |
| |
| final List<BundleEntity> bundleEntities = metadataService.getBundlesByBucketAndGroup(bucket.getIdentifier(), groupId); |
| bundleEntities.forEach(b -> { |
| final ExtensionRepoArtifact repoArtifact = new ExtensionRepoArtifact(); |
| repoArtifact.setBucketName(bucket.getName()); |
| repoArtifact.setGroupId(b.getGroupId()); |
| repoArtifact.setArtifactId(b.getArtifactId()); |
| repoArtifacts.add(repoArtifact); |
| }); |
| |
| return repoArtifacts; |
| } |
| |
| @Override |
| public SortedSet<ExtensionRepoVersionSummary> getExtensionRepoVersions(final Bucket bucket, final String groupId, final String artifactId) { |
| if (bucket == null) { |
| throw new IllegalArgumentException("Bucket cannot be null"); |
| } |
| |
| 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"); |
| } |
| |
| final SortedSet<ExtensionRepoVersionSummary> repoVersions = new TreeSet<>(); |
| |
| final List<BundleVersionEntity> versionEntities = metadataService.getBundleVersions(bucket.getIdentifier(), groupId, artifactId); |
| if (!versionEntities.isEmpty()) { |
| final BundleEntity bundleEntity = metadataService.getBundle(bucket.getIdentifier(), groupId, artifactId); |
| if (bundleEntity == null) { |
| // should never happen if the list of versions is not empty, but just in case |
| throw new ResourceNotFoundException("The specified extension bundle does not exist in this bucket"); |
| } |
| |
| versionEntities.forEach(v -> { |
| final ExtensionRepoVersionSummary repoVersion = new ExtensionRepoVersionSummary(); |
| repoVersion.setBucketName(bucket.getName()); |
| repoVersion.setGroupId(bundleEntity.getGroupId()); |
| repoVersion.setArtifactId(bundleEntity.getArtifactId()); |
| repoVersion.setVersion(v.getVersion()); |
| repoVersion.setAuthor(v.getCreatedBy()); |
| repoVersion.setTimestamp(v.getCreated().getTime()); |
| repoVersions.add(repoVersion); |
| }); |
| } |
| |
| return repoVersions; |
| } |
| |
| // ------ Helper Methods ------- |
| |
| private BundleContext getExtensionBundleContext(final BundleVersion bundleVersion) { |
| return getExtensionBundleContext(bundleVersion.getBucket(), bundleVersion.getBundle(), bundleVersion.getVersionMetadata()); |
| } |
| |
| private BundleContext getExtensionBundleContext(final Bucket bucket, final Bundle bundle, |
| final BundleVersionMetadata bundleVersionMetadata) { |
| return new StandardBundleContext.Builder() |
| .bundleType(getProviderBundleType(bundle.getBundleType())) |
| .bucketId(bucket.getIdentifier()) |
| .bucketName(bucket.getName()) |
| .bundleId(bundle.getIdentifier()) |
| .bundleGroupId(bundle.getGroupId()) |
| .bundleArtifactId(bundle.getArtifactId()) |
| .bundleVersion(bundleVersionMetadata.getVersion()) |
| .author(bundleVersionMetadata.getAuthor()) |
| .timestamp(bundleVersionMetadata.getTimestamp()) |
| .build(); |
| } |
| |
| private BucketEntity getBucketEntity(final String bucketIdentifier) { |
| // ensure the bucket exists |
| final BucketEntity existingBucket = metadataService.getBucketById(bucketIdentifier); |
| if (existingBucket == null) { |
| LOGGER.warn("The specified bucket id [{}] does not exist.", bucketIdentifier); |
| throw new ResourceNotFoundException("The specified bucket ID does not exist in this registry."); |
| } |
| return existingBucket; |
| } |
| |
| private BundleEntity getBundleEntity(final String bundleId) { |
| final BundleEntity existingBundle = metadataService.getBundle(bundleId); |
| if (existingBundle == null) { |
| LOGGER.warn("The specified extension bundle id [{}] does not exist.", bundleId); |
| throw new ResourceNotFoundException("The specified extension bundle ID does not exist."); |
| } |
| return existingBundle; |
| } |
| |
| } |