package ro.isdc.wro.maven.plugin.support;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.plexus.build.incremental.BuildContext;
/**
* Encapsulate the details about state persisted across multiple build runs. This class hides the details about the
* storage used to persist the data produced during a build. By default the {@link BuildContext} is used, but if it is
* not available - the alternate storage (a properties file stored in the file system) is used.
*
* @author Alex Objelean
* @created 1 Sep 2013
* @since 1.7.1
*/
public class BuildContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(BuildContextHolder.class);
private static final String ROOT_FOLDER_NAME = ".wro4j";
private static final String FALLBACK_STORAGE_FILE_NAME = "buildContext.properties";
/**
* Responsible for build storage persistence. Uses configured {@link BuildContext} as a primary storage object.
*/
private final BuildContext buildContext;
private final File buildDirectory;
private Properties fallbackStorage;
private File fallbackStorageFile;
private boolean incrementalBuildEnabled;
/**
* @return an instance of {@link BuildContextHolder} which uses temp folder as root location of persisted data.
*/
BuildContextHolder() {
this(null, null);
}
/**
* @param buildContext
* - The context provided by maven. If this is not provided, the default mechanism for persisting build data
* is used.
* @param buildDirectory
* the folder where the build output is created. If this value is null, the temp folder will be used.
*/
public BuildContextHolder(final BuildContext buildContext, final File buildDirectory) {
this.buildContext = buildContext;
this.buildDirectory = buildDirectory == null ? FileUtils.getTempDirectory() : buildDirectory;
try {
initFallbackStorage();
} catch (final IOException e) {
LOG.warn("Cannot use fallback storage: {}.", fallbackStorageFile, e);
}
}
private void initFallbackStorage()
throws IOException {
fallbackStorage = new Properties();
final File rootFolder = new File(buildDirectory, ROOT_FOLDER_NAME);
fallbackStorageFile = newFallbackStorageFile(rootFolder);
if (!fallbackStorageFile.exists()) {
fallbackStorageFile.getParentFile().mkdirs();
fallbackStorageFile.createNewFile();
LOG.debug("created fallback storage: {}", fallbackStorageFile);
} else {
fallbackStorage.load(new AutoCloseInputStream(new FileInputStream(fallbackStorageFile)));
LOG.debug("loaded fallback storage: {}", fallbackStorage);
}
}
/**
* @VisibleForTesting
*/
File newFallbackStorageFile(final File rootFolder) {
return new File(rootFolder, FALLBACK_STORAGE_FILE_NAME);
}
/**
* @VisibleForTesting
* @return the File where the fallback storage is persisted.
*/
File getFallbackStorageFile() {
return fallbackStorageFile;
}
/**
* @param key
* of the value to retrieve.
* @return the persisted value stored under the provided key. If no value exist, the returned result will be null.
*/
public String getValue(final String key) {
String value = null;
if (key != null) {
if (buildContext != null) {
value = (String) buildContext.getValue(key);
}
if (value == null) {
value = fallbackStorage.getProperty(key);
}
}
return value;
}
/**
* @param key
* to associate with the value to be persisted.
* @param value
* to persist.
*/
public void setValue(final String key, final String value) {
LOG.debug("storing key: '{}' and value: '{}'", key, value);
if (key != null) {
if (buildContext != null) {
buildContext.setValue(key, value);
}
// always use fallback
if (value != null) {
fallbackStorage.setProperty(key, value);
} else {
fallbackStorage.remove(key);
}
} else {
LOG.debug("Cannot store null key");
}
}
public void setIncrementalBuildEnabled(final boolean incrementalBuildEnabled) {
this.incrementalBuildEnabled = incrementalBuildEnabled;
}
/**
* @return the flag indicating the incremental build change. A build is incremental, when the modified resources
* should be processed only. Useful to avoid unnecessary processing when there is actually no change detected.
*/
public boolean isIncrementalBuild() {
return buildContext != null ? buildContext.isIncremental() || incrementalBuildEnabled : incrementalBuildEnabled;
}
/**
* Destroy the persisted storage and all stored data.
*/
public void destroy() {
fallbackStorage.clear();
FileUtils.deleteQuietly(fallbackStorageFile);
}
/**
* Persist the fallbackStorage to the fallbackStorageFile. This method should be invoked only once during build, since
* it is relatively expensive. Not invoking it, would break the incremental build feature.
*/
public void persist() {
OutputStream os = null;
try {
os = new FileOutputStream(fallbackStorageFile);
fallbackStorage.store(os, "Generated");
LOG.debug("fallback storage written to {}", fallbackStorageFile);
} catch (final IOException e) {
LOG.warn("Cannot persist fallback storage: {}.", fallbackStorageFile, e);
} finally {
IOUtils.closeQuietly(os);
}
}
}