/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2014 ForgeRock AS. */ package org.forgerock.openidm.patch; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.jar.JarOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import org.forgerock.openidm.patch.utils.FileUtil; /** * Provides basic patch infrastructure for generating OpenIDM patch bundles. */ public class Archive { private JarOutputStream jos = null; private final Set dirEntries; private static Archive instance = null; private File archiveDir = null; private File installDir = null; private final static Logger logger = Logger.getLogger("PatchLog"); /** * Location of the static files within the patch JAR archive. */ public final static String STATIC_FILES_ENTRY = "files"; /** * Archive constructor. */ public Archive() { this.dirEntries = new HashSet(); } /** * Initializes the patch and creates the patch archive bundle. * * @param installDir The target directory against which the patch is to be applied. * @param workingDir The working directory in which to store the archive bundle. * @param archiveName The named of the patch archive bundle. * @throws FileNotFoundException If the installDir or workingDir do not exist. * @throws IOException If an exception occurs while performing a I/O operation. */ public void initialize(File installDir, File workingDir, String archiveName) throws FileNotFoundException, IOException { DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); Date date = new Date(); archiveDir = new File(workingDir, dateFormat.format(date)); if (archiveDir.mkdirs()) { logger.log(Level.INFO, "Created patch archive directory: {0}", archiveDir); } this.installDir = installDir; if (archiveName == null || archiveName.isEmpty()) { archiveName = "backup.jar"; } FileOutputStream outputStream; outputStream = new FileOutputStream(new File(archiveDir, archiveName)); jos = new JarOutputStream(outputStream); } /** * Insert a file within the patch archive bundle. * * @param file The {@link File} to be inserted. */ public void insert(File file) { if (!file.exists() || !file.canRead()) { logger.log(Level.FINE, "File does not exist or could not be read during backup: {0}", file); } else { try { String relativePath = FileUtil.constructRelativePath(installDir, file); if (file.isFile()) { addStaticFile(file, relativePath); } else if (file.isDirectory()) { addDirectory(file, relativePath, true); } } catch (ZipException ex) { logger.log(Level.WARNING, "Unable to archive {0}", file); } catch (IOException ex) { logger.log(Level.SEVERE, "Failed to archive {0}", file); } } } /** * Get the directory in which the archive bundle resides. * * @return The archive directory. */ public File getArchiveDirectory() { return archiveDir; } private void addDirectory(File dirToCompress, String targetEntry, Boolean recurse) throws IOException, ZipException { File [] files = dirToCompress.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory() && recurse) { addDirectory(files[i], targetEntry + File.separator + files[i].getName(), true); } else { addStaticFile(files[i], targetEntry + File.separator + files[i].getName()); } } } private void addStaticFile(File fileToCompress, String targetEntry) throws IOException, ZipException { File entry = new File(STATIC_FILES_ENTRY, targetEntry); addParentDirectories(entry.getParentFile()); addFile(entry, fileToCompress); } private void addParentDirectories(File targetPath) throws IOException, ZipException { File parent = targetPath.getParentFile(); if (parent != null) { addParentDirectories(parent); } addDirectoryEntry(targetPath); } private void addDirectoryEntry(File targetPath) throws IOException, ZipException { if (!dirEntries.contains(targetPath.getPath())) { jos.putNextEntry(new ZipEntry(targetPath.getPath() + "/")); jos.closeEntry(); dirEntries.add(targetPath.getPath()); } } private void addFile(File targetEntry, File file) throws IOException, ZipException { if (file.isDirectory()) { addDirectoryEntry(targetEntry); } else if (file.isFile()) { FileInputStream fis = new FileInputStream(file); BufferedInputStream in = new BufferedInputStream(fis); try { ZipEntry entry = new ZipEntry(targetEntry.getPath()); jos.putNextEntry(entry); byte[] buffer = new byte[1024]; while (true) { int count = in.read(buffer); if (count == -1) { break; } jos.write(buffer, 0, count); } jos.closeEntry(); logger.log(Level.INFO, "Added {0} to archive.", targetEntry); } finally { in.close(); fis.close(); } } } /** * Close the archive and associated resources. * * @throws IOException If a failure occurs while releasing any associated resources. */ public void close() throws IOException { if (jos != null) { jos.close(); } } /** * Get an instance of the patch archive bundle. * * @return An instance of the {@link Archive}. */ public static Archive getInstance() { if (instance == null) { instance = new Archive(); } return instance; } }