ModelFileFinderImpl.java
// @formatter:off
/*******************************************************************************
*
* This file is part of JMad.
*
* Copyright (c) 2008-2011, CERN. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
// @formatter:on
/**
*
*/
package cern.accsoft.steering.jmad.modeldefs.io.impl;
import cern.accsoft.steering.jmad.domain.file.ModelFile;
import cern.accsoft.steering.jmad.domain.file.ModelFile.ModelFileLocation;
import cern.accsoft.steering.jmad.domain.file.ModelPathOffsets;
import cern.accsoft.steering.jmad.domain.file.ModelPathOffsetsImpl;
import cern.accsoft.steering.jmad.kernel.JMadKernel;
import cern.accsoft.steering.jmad.modeldefs.domain.SourceInformation;
import cern.accsoft.steering.jmad.modeldefs.domain.SourceInformation.SourceType;
import cern.accsoft.steering.jmad.modeldefs.io.ModelFileFinder;
import cern.accsoft.steering.jmad.util.JMadPreferences;
import cern.accsoft.steering.jmad.util.StreamUtil;
import cern.accsoft.steering.jmad.util.TempFileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static cern.accsoft.steering.jmad.util.ResourceUtil.canonicalizePath;
import static cern.accsoft.steering.jmad.util.ResourceUtil.prependPathOffset;
/**
* This is the implementation of a class that finds model-files.
*
* @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
*/
public class ModelFileFinderImpl implements ModelFileFinder {
private static final String DEFAULT_REPOSITORY_BASEPATH = ".";
/** the logger for the class */
private static final Logger LOGGER = LoggerFactory.getLogger(ModelFileFinderImpl.class);
/** The offsets for a specific model */
private ModelPathOffsets modelPathOffsets = new ModelPathOffsetsImpl();
/** The paths from which the model definition was loaded */
private SourceInformation sourceInformation = null;
/**
* The default strategy to search for repository files is to take them from the archive.
*/
private RepositoryFilePriority repositoryFilePriority = RepositoryFilePriority.ARCHIVE;
/** The preferences to find the resource path */
private JMadPreferences preferences;
/** The file util to create the temp-dirs. */
private TempFileUtil fileUtil;
/**
* the setter for the necessary offsets.
*
* @param modelPathOffsets the offsets to use.
*/
public void setModelFilePathOffsets(ModelPathOffsets modelPathOffsets) {
this.modelPathOffsets = modelPathOffsets;
}
@Override
public File getFile(ModelFile modelFile, JMadKernel kernel) {
return getFile(modelFile, path -> this.fileUtil.getOutputFile(kernel, path));
}
public File getFile(ModelFile modelFile, Function<String, File> tmpFileFunction) {
FileSource source = getFileSource(modelFile);
File file = source.getFile(this, modelFile, tmpFileFunction);
if (file == null) {
LOGGER.warn("Could not get model file '" + modelFile.getName() + "' from source '" + source + "'");
}
return file;
}
@Override
public InputStream getStream(ModelFile modelFile) {
FileSource source = getFileSource(modelFile);
return source.getStream(this, modelFile);
}
@Override
public Optional<File> getLocalSourceFile(ModelFile modelFile) {
SourceType sourceType = getSource();
if (SourceType.FILE == sourceType) {
return Optional.of(getInputFile(getSourceInformation().getRootPath(), getArchivePath(modelFile)));
}
return Optional.empty();
}
/**
* get the stream for a file from the archive (JAR or ZIP)
*
* @param modelFile the model file for which to get the stream
* @return the stream
*/
private InputStream getArchiveStream(ModelFile modelFile) {
SourceType sourceType = getSource();
String archivePath = getArchivePath(modelFile);
if (SourceType.JAR == sourceType) {
String path = prependPathOffset(archivePath, ModelDefinitionUtil.PACKAGE_OFFSET);
return ModelDefinitionUtil.BASE_CLASS.getResourceAsStream(canonicalizePath(path));
} else if (SourceType.ZIP == sourceType) {
/* 4) if there is an offset with in the archive, then also prepend this one */
String withinZipPath = prependPathOffset(archivePath, sourceInformation.getPathOffsetWithinArchive());
return getZipInputStream(getSourceInformation().getRootPath(), canonicalizePath(withinZipPath));
} else if (SourceType.FILE == sourceType) {
return getFileInputStream(getSourceInformation().getRootPath(), archivePath);
} else {
LOGGER.warn("Unhandled source type '" + sourceType + "'.");
return null;
}
}
private SourceType getSource() {
SourceInformation sourceInfo = getSourceInformation();
if (sourceInfo == null) {
return SourceType.JAR;
} else {
return sourceInfo.getSourceType();
}
}
private InputStream getFileInputStream(File baseDir, String relativePath) {
File file = getInputFile(baseDir, relativePath);
LOGGER.debug("Fetching file '" + file.getAbsolutePath() + "'");
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
LOGGER.warn("Could not open file '" + file.getAbsolutePath() + "'", e);
return null;
}
}
private File getInputFile(File baseDir, String relativePath) {
return new File(baseDir.getAbsolutePath() + File.separator + relativePath);
}
private InputStream getZipInputStream(File file, String entryName) {
LOGGER.debug("Fetching file '" + entryName + "' from zip file '" + file.getAbsolutePath() + "'");
ZipFile zipFile;
try {
zipFile = new ZipFile(file);
} catch (IOException e) {
LOGGER.error("Could not open zip file '" + file.getAbsolutePath() + "'");
return null;
}
ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) {
LOGGER.warn("Could not get entry '" + entryName + "' from zip file '" + zipFile + "'.");
return null;
}
try {
return zipFile.getInputStream(entry);
} catch (IOException e) {
LOGGER.error("Could not get input stream for entry '" + entryName + "'");
return null;
}
}
/**
* returns an accessible file with the given name relative to the model - basepath from the resources in the
* classpath or zip file.
*
* @param modelFile the {@link ModelFile} for which to get the rel file.
* @return the file
*/
private File getArchiveFile(ModelFile modelFile, Function<String, File> tmpFileFunction) {
String filename = getArchivePath(modelFile);
File file = tmpFileFunction.apply(filename);
/*
* For the moment all 'Archive types' (ZIP, JAR, and FILE) are treated the same. Maybe change this at some point
* so that real files are not copied to the tmp path, but only extracted if necessary.
*/
InputStream inputStream = getArchiveStream(modelFile);
if (StreamUtil.toFile(inputStream, file)) {
return file;
} else {
return null;
}
}
/**
* get the stream for a real file. (This only returns a valid stream if the file is a repository file)
*
* @param modelFile the model file for which to get the stream
* @return the input stream from the file
*/
private InputStream getRepositoryStream(ModelFile modelFile) {
File file = getRepositoryFile(modelFile);
if (file == null) {
return null;
} else {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
return null;
}
}
}
/**
* returns a file object that represents the real file in the repository. This only returns a non null value for a
* repository file
*
* @param modelFile the model file for which
* @return the file
*/
private File getRepositoryFile(ModelFile modelFile) {
String repoPath = getRepositoryPath(modelFile);
return new File(repoPath);
}
@Override
public String getArchivePath(ModelFile modelFile) {
/* 1) the name itself */
String resourcePath = modelFile.getName();
/* 2) the model definition - dependent offset */
resourcePath = prependPathOffset(resourcePath, modelFile.getLocation().getPathOffset(this.modelPathOffsets));
/* 3) the offset depending on the type (Resource or repo file; can be overridden by the model definition) */
resourcePath = prependPathOffset(resourcePath, modelFile.getLocation().getResourcePrefix(this.modelPathOffsets));
return resourcePath;
}
@Override
public String getRepositoryPath(ModelFile modelFile) {
String repoPath = modelFile.getName();
repoPath = prependPathOffset(repoPath, getRepositoryPathOffset());
repoPath = getRepositoryBasePath() + File.separator + repoPath;
return repoPath;
}
private String getRepositoryBasePath() {
String basePath = getPreferences().getModelRepositoryBasePath();
if ((basePath == null) && (sourceInformation != null)
&& (SourceType.FILE.equals(sourceInformation.getSourceType()))) {
basePath = sourceInformation.getRootPath().getAbsolutePath();
}
if (basePath == null) {
basePath = DEFAULT_REPOSITORY_BASEPATH;
}
return basePath;
}
private String getRepositoryPathOffset() {
return this.modelPathOffsets.getRepositoryOffset();
}
public void setPreferences(JMadPreferences preferences) {
this.preferences = preferences;
}
private JMadPreferences getPreferences() {
if (this.preferences == null) {
LOGGER.warn("Preferences not set. Maybe config error.");
}
return preferences;
}
public void setFileUtil(TempFileUtil fileUtil) {
this.fileUtil = fileUtil;
}
private TempFileUtil getFileUtil() {
if (this.fileUtil == null) {
LOGGER.warn("FileUtil not set. Maybe config error.");
}
return fileUtil;
}
@Override
public RepositoryFilePriority getRepositoryFilePriority() {
return this.repositoryFilePriority;
}
@Override
public void setRepositoryFilePriority(RepositoryFilePriority priority) {
this.repositoryFilePriority = priority;
}
/**
* decides from where the file will be finally taken.
*
* @param modelFile the modelFile for which to decide
* @return the FileSource
*/
private FileSource getFileSource(ModelFile modelFile) {
if (ModelFileLocation.RESOURCE.equals(modelFile.getLocation())) {
return FileSource.ARCHIVE;
} else if (ModelFileLocation.REPOSITORY.equals(modelFile.getLocation())) {
if (RepositoryFilePriority.ARCHIVE.equals(getRepositoryFilePriority())) {
/*
* If the file can be retrieved from the archive then the source shall be ARCHIVE
*/
if (isAvailableInArchive(modelFile)) {
return FileSource.ARCHIVE;
} else {
return FileSource.REPOSITORY;
}
} else if (RepositoryFilePriority.REPOSITORY.equals(getRepositoryFilePriority())) {
if (isAvailableInRepository(modelFile)) {
return FileSource.REPOSITORY;
} else {
return FileSource.ARCHIVE;
}
} else {
LOGGER.warn("Unhandled RepositoryFilePriority '" + getRepositoryFilePriority()
+ "'. Will try to get modelfile from archive.");
return FileSource.ARCHIVE;
}
} else {
LOGGER.warn("Unhandled ModelFileLocation '" + modelFile.getLocation()
+ "'. Will try to get modelfile from archive.");
return FileSource.ARCHIVE;
}
}
/**
* checks if the file is available in the archive
*
* @param modelFile the model file to check
* @return true if it exists, false if not
*/
private boolean isAvailableInArchive(ModelFile modelFile) {
InputStream archiveStream = getArchiveStream(modelFile);
if (archiveStream != null) {
try {
archiveStream.close();
} catch (IOException e) {
LOGGER.error("Error while closing input stream.", e);
}
return true;
}
return false;
}
/**
* checks if the file is available in the repository
*
* @param modelFile the file to check
* @return true if it exists, false if not
*/
private boolean isAvailableInRepository(ModelFile modelFile) {
File file = getRepositoryFile(modelFile);
if ((file != null) && (file.exists())) {
return true;
}
return false;
}
public void setSourceInformation(SourceInformation sourceInformation) {
this.sourceInformation = sourceInformation;
}
private SourceInformation getSourceInformation() {
return sourceInformation;
}
/**
* this enum indicates from where the repository-file is taken. It is the result of a combination of
* {@link ModelFileFinder.RepositoryFilePriority} and the availability.
*
* @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
*/
private static enum FileSource {
ARCHIVE {
@Override
public File getFile(ModelFileFinderImpl fileFinder, ModelFile modelFile,
Function<String, File> tmpFileFunction) {
return fileFinder.getArchiveFile(modelFile, tmpFileFunction);
}
@Override
public InputStream getStream(ModelFileFinderImpl fileFinder, ModelFile modelFile) {
return fileFinder.getArchiveStream(modelFile);
}
},
REPOSITORY {
@Override
public File getFile(ModelFileFinderImpl fileFinder, ModelFile modelFile,
Function<String, File> tmpFileFunction) {
return fileFinder.getRepositoryFile(modelFile);
}
@Override
public InputStream getStream(ModelFileFinderImpl fileFinder, ModelFile modelFile) {
return fileFinder.getRepositoryStream(modelFile);
}
};
public abstract File getFile(ModelFileFinderImpl fileFinder, ModelFile modelFile,
Function<String, File> tmpFileFunction);
public abstract InputStream getStream(ModelFileFinderImpl fileFinder, ModelFile modelFile);
}
}