001/* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2013 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * SonarQube is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.home.cache; 021 022import org.apache.commons.io.FileUtils; 023import org.sonar.home.log.Log; 024 025import javax.annotation.CheckForNull; 026 027import java.io.File; 028import java.io.IOException; 029import java.util.Random; 030 031/** 032 * This class is responsible for managing Sonar batch file cache. You can put file into cache and 033 * later try to retrieve them. MD5 is used to differentiate files (name is not secure as files may come 034 * from different Sonar servers and have same name but be actually different, and same for SNAPSHOTs). 035 */ 036public class FileCache { 037 038 private static final int TEMP_FILE_ATTEMPTS = 1000; 039 040 private final File dir, tmpDir; 041 private final FileHashes hashes; 042 private final Log log; 043 044 FileCache(File dir, Log log, FileHashes fileHashes) { 045 this.hashes = fileHashes; 046 this.log = log; 047 this.dir = createDir(dir, log, "user cache"); 048 log.info(String.format("User cache: %s", dir.getAbsolutePath())); 049 this.tmpDir = createDir(new File(dir, "_tmp"), log, "temp dir"); 050 } 051 052 public static FileCache create(File dir, Log log) { 053 return new FileCache(dir, log, new FileHashes()); 054 } 055 056 public File getDir() { 057 return dir; 058 } 059 060 /** 061 * Look for a file in the cache by its filename and md5 checksum. If the file is not 062 * present then return null. 063 */ 064 @CheckForNull 065 public File get(String filename, String hash) { 066 File cachedFile = new File(new File(dir, hash), filename); 067 if (cachedFile.exists()) { 068 return cachedFile; 069 } 070 log.debug(String.format("No file found in the cache with name %s and hash %s", filename, hash)); 071 return null; 072 } 073 074 public interface Downloader { 075 void download(String filename, File toFile) throws IOException; 076 } 077 078 public File get(String filename, String hash, Downloader downloader) { 079 // Does not fail if another process tries to create the directory at the same time. 080 File hashDir = hashDir(hash); 081 File targetFile = new File(hashDir, filename); 082 if (!targetFile.exists()) { 083 File tempFile = newTempFile(); 084 download(downloader, filename, tempFile); 085 String downloadedHash = hashes.of(tempFile); 086 if (!hash.equals(downloadedHash)) { 087 throw new IllegalStateException("INVALID HASH: File " + tempFile.getAbsolutePath() + " was expected to have hash " + hash 088 + " but was downloaded with hash " + downloadedHash); 089 } 090 mkdirQuietly(hashDir); 091 renameQuietly(tempFile, targetFile); 092 } 093 return targetFile; 094 } 095 096 private void download(Downloader downloader, String filename, File tempFile) { 097 try { 098 downloader.download(filename, tempFile); 099 } catch (IOException e) { 100 throw new IllegalStateException("Fail to download " + filename + " to " + tempFile, e); 101 } 102 } 103 104 private void renameQuietly(File sourceFile, File targetFile) { 105 boolean rename = sourceFile.renameTo(targetFile); 106 // Check if the file was cached by another process during download 107 if (!rename && !targetFile.exists()) { 108 log.warn(String.format("Unable to rename %s to %s", sourceFile.getAbsolutePath(), targetFile.getAbsolutePath())); 109 log.warn(String.format("A copy/delete will be tempted but with no garantee of atomicity")); 110 try { 111 FileUtils.moveFile(sourceFile, targetFile); 112 } catch (IOException e) { 113 throw new IllegalStateException("Fail to move " + sourceFile.getAbsolutePath() + " to " + targetFile, e); 114 } 115 } 116 } 117 118 private File hashDir(String hash) { 119 return new File(dir, hash); 120 } 121 122 private void mkdirQuietly(File hashDir) { 123 try { 124 FileUtils.forceMkdir(hashDir); 125 } catch (IOException e) { 126 throw new IllegalStateException("Fail to create cache directory: " + hashDir, e); 127 } 128 } 129 130 private File newTempFile() { 131 String baseName = System.currentTimeMillis() + "-"; 132 Random random = new Random(); 133 for (int counter = 0; counter < TEMP_FILE_ATTEMPTS; counter++) { 134 try { 135 String filename = baseName + random.nextInt(1000); 136 File tempFile = new File(tmpDir, filename); 137 if (tempFile.createNewFile()) { 138 return tempFile; 139 } 140 } catch (IOException e) { 141 // ignore except the last try 142 if (counter == TEMP_FILE_ATTEMPTS - 1) { 143 throw new IllegalStateException(); 144 } 145 } 146 } 147 throw new IllegalStateException("Fail to create temporary file in " + tmpDir); 148 } 149 150 private File createDir(File dir, Log log, String debugTitle) { 151 if (!dir.isDirectory() || !dir.exists()) { 152 log.debug("Create : " + dir.getAbsolutePath()); 153 try { 154 FileUtils.forceMkdir(dir); 155 } catch (IOException e) { 156 throw new IllegalStateException("Unable to create " + debugTitle + dir.getAbsolutePath(), e); 157 } 158 } 159 return dir; 160 } 161 162}