2 * Copyright © 2017-2018 AT&T Intellectual Property.
3 * Modifications Copyright © 2019 Bell Canada.
4 * Modifications Copyright © 2019 Nordix Foundation.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org.onap.ccsdk.cds.controllerblueprints.core.utils
21 import com.google.common.base.Predicates
22 import org.apache.commons.compress.archivers.ArchiveEntry
23 import org.apache.commons.compress.archivers.ArchiveInputStream
24 import org.apache.commons.compress.archivers.ArchiveOutputStream
25 import org.apache.commons.compress.archivers.tar.TarArchiveEntry
26 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
27 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
28 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
29 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
30 import org.apache.commons.compress.archivers.zip.ZipFile
31 import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
32 import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
33 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintProcessorException
34 import org.slf4j.LoggerFactory
35 import java.io.BufferedInputStream
36 import java.io.ByteArrayInputStream
37 import java.io.ByteArrayOutputStream
38 import java.io.Closeable
40 import java.io.FileOutputStream
41 import java.io.IOException
42 import java.io.InputStream
43 import java.io.InputStreamReader
44 import java.io.OutputStream
45 import java.nio.file.FileVisitResult
46 import java.nio.file.Files
47 import java.nio.file.Path
48 import java.nio.file.SimpleFileVisitor
49 import java.nio.file.attribute.BasicFileAttributes
50 import java.util.Enumeration
51 import java.util.function.Predicate
52 import java.util.zip.Deflater
54 enum class ArchiveType {
59 class BlueprintArchiveUtils {
63 private val log = LoggerFactory.getLogger(BlueprintArchiveUtils::class.java)
66 * Create a new Zip from a root directory
68 * @param source the base directory
69 * @param destination the output filename
72 fun compress(source: File, destination: File, archiveType: ArchiveType = ArchiveType.Zip): Boolean {
74 if (!destination.parentFile.exists()) {
75 destination.parentFile.mkdirs()
77 destination.createNewFile()
78 val ignoreZipFiles = Predicate<Path> { path -> !path.endsWith(".zip") && !path.endsWith(".ZIP") }
79 FileOutputStream(destination).use { out ->
80 compressFolder(source.toPath(), out, archiveType, pathFilter = ignoreZipFiles)
82 } catch (e: Exception) {
83 log.error("Fail to compress folder($source) to path(${destination.path})", e)
90 * In-memory compress an entire folder.
94 archiveType: ArchiveType = ArchiveType.Zip,
95 compressionLevel: Int = Deflater.NO_COMPRESSION
97 return compressFolder(baseDir, ByteArrayOutputStream(), archiveType, compressionLevel = compressionLevel)
102 * Compress an entire folder.
104 * @param baseDir path of base folder to be packaged.
105 * @param output the output stream
106 * @param pathFilter filter to ignore files based on its path.
107 * @param compressionLevel the wanted compression level.
108 * @param fixedModificationTime to force every entry to have this modification time.
109 * Useful for reproducible operations, like tests, for example.
111 private fun <T> compressFolder(
114 archiveType: ArchiveType,
115 pathFilter: Predicate<Path> = Predicates.alwaysTrue(),
116 compressionLevel: Int = Deflater.DEFAULT_COMPRESSION,
117 fixedModificationTime: Long? = null
119 where T : OutputStream {
120 val stream: ArchiveOutputStream = if (archiveType == ArchiveType.Zip)
121 ZipArchiveOutputStream(output).apply { setLevel(compressionLevel) }
123 TarArchiveOutputStream(GzipCompressorOutputStream(output))
128 object : SimpleFileVisitor<Path>() {
129 @Throws(IOException::class)
130 override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
131 if (pathFilter.test(file)) {
132 var archiveEntry: ArchiveEntry = aos.createArchiveEntry(
134 baseDir.relativize(file).toString()
136 if (archiveType == ArchiveType.Zip) {
137 val entry = archiveEntry as ZipArchiveEntry
138 fixedModificationTime?.let {
143 aos.putArchiveEntry(archiveEntry)
144 Files.copy(file, aos)
145 aos.closeArchiveEntry()
147 return FileVisitResult.CONTINUE
150 @Throws(IOException::class)
151 override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
152 var archiveEntry: ArchiveEntry?
153 if (archiveType == ArchiveType.Zip) {
154 val entry = ZipArchiveEntry(baseDir.relativize(dir).toString() + "/")
155 fixedModificationTime?.let {
160 archiveEntry = TarArchiveEntry(baseDir.relativize(dir).toString() + "/")
161 aos.putArchiveEntry(archiveEntry)
162 aos.closeArchiveEntry()
163 return FileVisitResult.CONTINUE
171 private fun getDefaultEncoding(): String? {
172 val bytes = byteArrayOf('D'.toByte())
173 val inputStream: InputStream = ByteArrayInputStream(bytes)
174 val reader = InputStreamReader(inputStream)
175 return reader.encoding
178 fun deCompress(archiveFile: File, targetPath: String, archiveType: ArchiveType = ArchiveType.Zip): File {
179 var enumeration: ArchiveEnumerator? = null
180 if (archiveType == ArchiveType.Zip) {
181 val zipArchive = ZipFile(archiveFile, getDefaultEncoding())
182 enumeration = ArchiveEnumerator(zipArchive)
184 var tarGzArchiveIs: InputStream = BufferedInputStream(archiveFile.inputStream())
185 tarGzArchiveIs = GzipCompressorInputStream(tarGzArchiveIs)
186 val tarGzArchive: ArchiveInputStream = TarArchiveInputStream(tarGzArchiveIs)
187 enumeration = ArchiveEnumerator(tarGzArchive)
191 while (enumeration!!.hasMoreElements()) {
192 val entry: ArchiveEntry? = enumeration.nextElement()
193 val destFilePath = File(targetPath, entry!!.name)
194 destFilePath.parentFile.mkdirs()
196 if (entry!!.isDirectory)
199 val bufferedIs = BufferedInputStream(enumeration.getInputStream(entry))
200 destFilePath.outputStream().buffered(1024).use { bos ->
201 bufferedIs.copyTo(bos)
204 if (!enumeration.getHasSharedEntryInputStream())
209 val destinationDir = File(targetPath)
210 check(destinationDir.isDirectory && destinationDir.exists()) {
211 throw BlueprintProcessorException("failed to decompress blueprint(${archiveFile.absolutePath}) to ($targetPath) ")
214 return destinationDir
218 class ArchiveEnumerator : Enumeration<ArchiveEntry>, Closeable {
220 private val zipArchive: ZipFile?
221 private val zipEnumeration: Enumeration<ZipArchiveEntry>?
222 private val archiveStream: ArchiveInputStream?
223 private var nextEntry: ArchiveEntry? = null
224 private val hasSharedEntryInputStream: Boolean
226 constructor(zipFile: ZipFile) {
228 zipEnumeration = zipFile.entries
230 hasSharedEntryInputStream = false
233 constructor(archiveStream: ArchiveInputStream) {
234 this.archiveStream = archiveStream
236 zipEnumeration = null
237 hasSharedEntryInputStream = true
240 fun getHasSharedEntryInputStream(): Boolean {
241 return hasSharedEntryInputStream
244 fun getInputStream(entry: ArchiveEntry): InputStream? {
245 return if (zipArchive != null)
246 zipArchive?.getInputStream(entry as ZipArchiveEntry?)
251 override fun hasMoreElements(): Boolean {
252 if (zipEnumeration != null)
253 return zipEnumeration?.hasMoreElements()
254 else if (archiveStream != null) {
255 nextEntry = archiveStream.nextEntry
256 if (nextEntry != null && !archiveStream.canReadEntryData(nextEntry))
257 return hasMoreElements()
258 return nextEntry != null
263 override fun nextElement(): ArchiveEntry? {
264 if (zipEnumeration != null)
265 nextEntry = zipEnumeration.nextElement()
266 else if (archiveStream != null) {
267 if (nextEntry == null)
268 nextEntry = archiveStream.nextEntry
273 override fun close() {
274 if (zipArchive != null)
276 else archiveStream?.close()