/*****************************************************************************
* This file is part of Rinzo
*
* Author: Claudio Cancinos
* WWW: https://sourceforge.net/projects/editorxml
* Copyright (C): 2008, Claudio Cancinos
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; If not, see <http://www.gnu.org/licenses/>
****************************************************************************/
package ar.com.tadp.xml.rinzo.core.resources.cache;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import org.eclipse.core.runtime.jobs.Job;
import ar.com.tadp.xml.rinzo.core.utils.FileUtils;
/**
* Used to cache remote resources.
*
* @author ccancinos
*/
public class DocumentCache {
private static final String CACHED_FILES_PREFIX = "f";
private String storePathURL;
private File cacheDefinitionsFile;
private Collection<DocumentStructureDeclaration> entries;
private Map<String, DocumentStructureDeclaration> publicNameToEntry;
private Map<String, DocumentStructureDeclaration> absoluteNameToEntry;
private int fMaxRel;
private CacheDefinitionsSerializer serializer = new CacheDefinitionsSerializer();
private static DocumentCache instance;
private DocumentCache() {
this.entries = new LinkedList<DocumentStructureDeclaration>();
this.publicNameToEntry = new HashMap<String, DocumentStructureDeclaration>();
this.absoluteNameToEntry = new HashMap<String, DocumentStructureDeclaration>();
this.fMaxRel = 0;
}
public static synchronized DocumentCache getInstance() {
if (instance == null) {
instance = new DocumentCache();
}
return instance;
}
/**
* Retorna la url del archivo local.
* Si el archivo buscado no es local, se devuelve la url del archivo que lo cachea.
* Si el archivo buscado es local, se devuelve la misma url que fue pedida.
*/
public String getLocation(String publicName, String absoluteName) {
File inputFile = new File(absoluteName);
if (inputFile.exists()) {
return absoluteName;
}
if(!this.contains(publicName, absoluteName)) {
Map<String, String> storeFiles = new HashMap<String, String>();
storeFiles.put(publicName, absoluteName);
this.storeAll(storeFiles, false);
}
return this.get(publicName, absoluteName);
}
public Map<String, String> getAllLocations(Collection<DocumentStructureDeclaration> schemaDefinitions, String fileName) {
Map<String, String> locations = new HashMap<String, String>();
Map<String, String> storeFiles = new HashMap<String, String>();
for (DocumentStructureDeclaration structureDeclaration : schemaDefinitions) {
try {
String publicName = structureDeclaration.getSystemId();
String absoluteName = FileUtils.resolveURI(fileName, publicName).toString();
File inputFile = new File(absoluteName);
if (inputFile.exists()) {
locations.put(publicName, absoluteName);
} else {
if(!this.contains(publicName, absoluteName)) {
storeFiles.put(publicName, absoluteName);
} else {
locations.put(publicName, this.get(publicName, absoluteName));
}
}
} catch (URISyntaxException e) {
// DO NOTHING, PROCESS NEXT FILE
}
}
if (!storeFiles.isEmpty()) {
this.storeAll(storeFiles, true);
for (Map.Entry<String, String> storedFiles : storeFiles.entrySet()) {
locations.put(storedFiles.getKey(), this.get(storedFiles.getKey(), storedFiles.getValue()));
}
}
return locations;
}
/**
* Se encarga de cachear el documento con el nombre p�blico y absoluto
* especificado
*/
private void storeAll(Map<String, String> storeFiles, boolean asUserJob) {
if (!storeFiles.isEmpty()) {
Job job = new DownloadFilesJob(storeFiles);
job.setUser(asUserJob);
job.schedule();
}
}
/**
* Given a base path and a url, this method returns the path to the
* corresponding cache file, or the absolute remote url to the resource
*
* If url is a path to a inexistent file in the cache, it resolve the file to the baseURL.
*
* @param baseURL
* @param url
* @param systemId
* @return
*/
public String getLocationFromBase(String baseURL, String publicId, String systemId) {
String path;
try {
path = FileUtils.resolveURI(baseURL, systemId).toString();
if(path.startsWith("file:") && path.contains(".cache/") && !new File(path).exists()) {
path = path.substring(path.indexOf(".cache/")+7);
path = FileUtils.resolveURI(baseURL, path).toString();
}
return getLocation(publicId, path);
} catch (URISyntaxException e) {
return getLocation(publicId, systemId);
}
}
public synchronized boolean contains(String publicName, String absoluteRealName) {
return FileUtils.exists(this.get(publicName, absoluteRealName));
}
public synchronized void innerStore(String publicName, String absoluteRealName) {
if(contains(publicName, absoluteRealName)) {
return;
}
DocumentStructureDeclaration structureDeclaration = this.findEntry(publicName, absoluteRealName);
DocumentStructureDeclaration newEntry = null;
File outputFile = null;
int fileIndex = this.fMaxRel;
if(structureDeclaration != null) {
if(!FileUtils.exists(structureDeclaration.getLocalCachedName())) {
outputFile = new File(structureDeclaration.getLocalCachedName());
} else {
outputFile = this.getNewCacheFile(absoluteRealName.substring(absoluteRealName.lastIndexOf(".")));
structureDeclaration.setLocalCachedName(outputFile.getName());
}
} else {
outputFile = this.getNewCacheFile(absoluteRealName.substring(absoluteRealName.lastIndexOf(".")));
newEntry = new DocumentStructureDeclaration(publicName, absoluteRealName, outputFile.getName());
}
try {
FileUtils.saveFile(absoluteRealName, outputFile);
if (outputFile.length() > 0) {
if(structureDeclaration == null) {
this.addEntry(newEntry);
this.saveCacheDefinitions();
}
} else {
FileUtils.safeDelete(outputFile);
if (fileIndex != this.fMaxRel) {
this.fMaxRel--;
}
}
} catch (Exception e) {
FileUtils.safeDelete(outputFile);
if (fileIndex != this.fMaxRel) {
this.fMaxRel--;
}
}
}
public void clear() {
this.entries.clear();
this.absoluteNameToEntry.clear();
this.publicNameToEntry.clear();
this.fMaxRel = 0;
saveCacheDefinitions();
clearCachedFiles();
}
/**
* @param urlLocation
* url de la ubicaci�n del directorio donde se cachear�n los
* documentos
*/
public void setCacheLocation(String urlLocation) throws IOException {
if (!urlLocation.startsWith("file:"))
throw new IllegalArgumentException("Must be a file: URL " + urlLocation);
String pathLocation = FileUtils.fileUrlToPath(urlLocation);
File file = new File(pathLocation);
if (file.exists()) {
if (!file.isDirectory())
throw new IllegalArgumentException("Not a directory " + pathLocation);
} else {
if (!file.mkdir())
throw new IllegalArgumentException("Can't make directory " + pathLocation);
}
if (!urlLocation.endsWith("/")) {
urlLocation = urlLocation + "/";
}
this.storePathURL = urlLocation;
String cacheDefinitnionsPath = FileUtils.fileUrlToPath(this.storePathURL + "cacheDefinitions.xml");
this.cacheDefinitionsFile = new File(cacheDefinitnionsPath);
if (this.cacheDefinitionsFile.exists()) {
this.loadCacheDefinitions();
} else {
if (!this.cacheDefinitionsFile.createNewFile())
throw new RuntimeException("Unable to create cache definitions file at " + cacheDefinitnionsPath);
this.saveCacheDefinitions();
}
}
/**
* @return url de la ubicaci�n del directorio donde se cachean los
* documentos
*/
public String getCacheLocation() {
return this.storePathURL;
}
/**
* Retorna el nombre de la url al documento
*/
private String get(String publicName, String absoluteName) {
DocumentStructureDeclaration structureDeclaration = this.findEntry(publicName, absoluteName);
String fileLocation = structureDeclaration == null ? null : structureDeclaration.getLocalCachedName();
return fileLocation != null ? FileUtils
.fileUrlToPath(this.getCacheLocation()+ fileLocation) :
absoluteName;
}
private void saveCacheDefinitions() {
this.serializer.saveCacheDefinitions(this.cacheDefinitionsFile, this.entries);
}
private void loadCacheDefinitions() throws IOException {
for (DocumentStructureDeclaration declaration : this.serializer.getCacheDefinitions(this.cacheDefinitionsFile)) {
this.addEntry(declaration);
}
}
private void clearCachedFiles() {
File storeDirectory = new File(FileUtils.fileUrlToPath(this.storePathURL));
if (storeDirectory.exists()) {
File[] files = storeDirectory.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isFile() && files[i].getName().startsWith(CACHED_FILES_PREFIX)) {
FileUtils.safeDelete(files[i]);
}
}
}
}
private void addEntry(DocumentStructureDeclaration structureDeclaration) {
this.entries.add(structureDeclaration);
if (structureDeclaration.getPublicId() != null) {
publicNameToEntry.put(structureDeclaration.getPublicId(), structureDeclaration);
}
if (structureDeclaration.getSystemId() != null) {
absoluteNameToEntry.put(structureDeclaration.getSystemId(), structureDeclaration);
}
}
private DocumentStructureDeclaration findEntry(String publicName, String absoluteName) {
DocumentStructureDeclaration structureDeclaration = publicName != null ? publicNameToEntry.get(publicName) : null;
if (structureDeclaration == null) {
structureDeclaration = (absoluteName != null) ? absoluteNameToEntry.get(absoluteName) : null;
}
return structureDeclaration;
}
/**
* Retorna un @link{File} correspondiente al nuevo archivo donde se podr� cachear un documento
*/
private File getNewCacheFile(String fileExtension) {
try {
File file = null;
do {
String relativeCachedName = CACHED_FILES_PREFIX + ++fMaxRel + fileExtension;
String fullPathRelativeCachedName = FileUtils.fileUrlToPath(storePathURL + relativeCachedName);
file = new File(fullPathRelativeCachedName);
} while (file.exists());
file.createNewFile();
return file;
} catch (Exception exception) {
throw new RuntimeException("Error trying to create a file to save in cache a document", exception);
}
}
}