class NarUnpacker {

    public static ExtensionMapping unpackNars(final NiFiProperties props) {
        final List<Path> narLibraryDirs = props.getNarLibraryDirectories();
        final File frameworkWorkingDir = props.getFrameworkWorkingDirectory();
        final File extensionsWorkingDir = props.getExtensionsWorkingDirectory();
        final File docsWorkingDir = props.getComponentDocumentationWorkingDirectory();

        try {
            File unpackedFramework = null;
            final Set<File> unpackedExtensions = new HashSet<>();
            final List<File> narFiles = new ArrayList<>();

            // make sure the nar directories are there and accessible
            FileUtils.ensureDirectoryExistAndCanReadAndWrite(frameworkWorkingDir);
            FileUtils.ensureDirectoryExistAndCanReadAndWrite(extensionsWorkingDir);
            FileUtils.ensureDirectoryExistAndCanReadAndWrite(docsWorkingDir);

            for (Path narLibraryDir : narLibraryDirs) {

                File narDir = narLibraryDir.toFile();

                // Test if the source NARs can be read
                FileUtils.ensureDirectoryExistAndCanRead(narDir);

                File[] dirFiles = narDir.listFiles(NAR_FILTER);
                if (dirFiles != null) {
                    List<File> fileList = Arrays.asList(dirFiles);
                    narFiles.addAll(fileList);
                }
            }

            if (!narFiles.isEmpty()) {
                final long startTime = System.nanoTime();
                logger.info("Expanding " + narFiles.size() + " NAR files with all processors...");
                for (File narFile : narFiles) {
                    logger.debug("Expanding NAR file: " + narFile.getAbsolutePath());

                    // get the manifest for this nar
                    try (final JarFile nar = new JarFile(narFile)) {
                        final Manifest manifest = nar.getManifest();

                        // lookup the nar id
                        final Attributes attributes = manifest.getMainAttributes();
                        final String narId = attributes.getValue("Nar-Id");

                        // determine if this is the framework
                        if (NarClassLoaders.FRAMEWORK_NAR_ID.equals(narId)) {
                            if (unpackedFramework != null) {
                                throw new IllegalStateException(
                                        "Multiple framework NARs discovered. Only one framework is permitted.");
                            }

                            unpackedFramework = unpackNar(narFile, frameworkWorkingDir);
                        } else {
                            unpackedExtensions.add(unpackNar(narFile, extensionsWorkingDir));
                        }
                    }
                }

                // ensure we've found the framework nar
                if (unpackedFramework == null) {
                    throw new IllegalStateException("No framework NAR found.");
                } else if (!unpackedFramework.canRead()) {
                    throw new IllegalStateException("Framework NAR cannot be read.");
                }

                // Determine if any nars no longer exist and delete their
                // working directories. This happens
                // if a new version of a nar is dropped into the lib dir.
                // ensure no old framework are present
                final File[] frameworkWorkingDirContents = frameworkWorkingDir.listFiles();
                if (frameworkWorkingDirContents != null) {
                    for (final File unpackedNar : frameworkWorkingDirContents) {
                        if (!unpackedFramework.equals(unpackedNar)) {
                            FileUtils.deleteFile(unpackedNar, true);
                        }
                    }
                }

                // ensure no old extensions are present
                final File[] extensionsWorkingDirContents = extensionsWorkingDir.listFiles();
                if (extensionsWorkingDirContents != null) {
                    for (final File unpackedNar : extensionsWorkingDirContents) {
                        if (!unpackedExtensions.contains(unpackedNar)) {
                            FileUtils.deleteFile(unpackedNar, true);
                        }
                    }
                }
                final long endTime = System.nanoTime();
                logger.info("NAR loading process took " + (endTime - startTime) + " nanoseconds.");
            }

            // attempt to delete any docs files that exist so that any
            // components that have been removed
            // will no longer have entries in the docs folder
            final File[] docsFiles = docsWorkingDir.listFiles();
            if (docsFiles != null) {
                for (final File file : docsFiles) {
                    FileUtils.deleteFile(file, true);
                }
            }

            final ExtensionMapping extensionMapping = new ExtensionMapping();
            mapExtensions(extensionsWorkingDir, docsWorkingDir, extensionMapping);
            return extensionMapping;
        } catch (IOException e) {
            logger.warn("Unable to load NAR library bundles due to " + e
                    + " Will proceed without loading any further Nar bundles");
            if (logger.isDebugEnabled()) {
                logger.warn("", e);
            }
        }

        return null;
    }

    private  NarUnpacker();

}