![JAR search and dependency download from the Maven repository](/logo.png)
protoj.lang.DirconfFeature Maven / Gradle / Ivy
Show all versions of protoj-jdk6 Show documentation
/**
* Copyright 2009 Ashley Williams
*
* 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.
*/
package protoj.lang;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.FilterSet;
import org.apache.tools.ant.types.FilterSet.FiltersFile;
import protoj.util.AntTarget;
/**
* Configures the project by overlaying the contents of a profile directory onto
* the project directory. A profile directory can have any content whatsoever,
* but usually it will contain a similar directory structure to the project
* itself and contain xml and property files that should replace those already
* existing in the project.
*
* Also uniquely for properties files, any properties will automatically get
* merged with those where a a destination properties file already exists, with
* the profile properties taking precedence over the project properties. Here is
* an example of a profile for a developer:
*
*
* ˜/dev/myproj/
* |
* |____bin/ copy will happen here
* |
* |____conf/ip-addresses.properties merge will happen here
* | |
* | |____profile/ create profiles under here
* | |
* | |____jonny/ an example profile directory
* | |
* | |____bin/useful-dev-script.sh
* | |
* | |____conf/ip-addresses.properties
* |
* |...
*
*
* So the developer has a special script that he likes to use and therefore has
* placed it in the bin directory under his profile. Also it seems also that the
* project needs to know various ip addresses in order to work correctly.
* Therefore he has placed his inside his conf directory, with the knowledge
* that after configuration, his properties will get merged into the properties
* file in the project directory and also take precedence.
*
* Interpolated properties are also supported, which basically means all ${var}
* property placeholders get resolved during configuration in any text files
* specified in a profile. This can be especially useful for the many tools that
* are unable to interpolate properties at runtime for themselves. It's a good
* idea to set interpolation permanently on or off since the two modes aren't
* compatible. So the decision must be made at the API level at compile time by
* specifying true or false in the call to StandardProject.initConfig().
*
* A profile directory can live anywhere. For example one arrangement is to use
* the home directory and store a choice of profile directories underneath. That
* way the profiles can be re-applied between application installations.
*
* The following options are supported:
*
*
* - -undo: when specified all files in the project hierarchy that match the
* files in the profile hierarchy are deleted. Really this is only a partial
* undo since no state is saved for the original configure command, so
* properties files for example also get deleted rather than unmerged.
*
*
* @author Ashley Williams
*
*/
public final class DirconfFeature {
/**
* See {@link #getDelegate()}.
*/
private final StandardProject parent;
/**
* See {@link #getWorkingDir()}.
*/
private File workingDir;
/**
* See {@link #DirconfFeature(StandardProject, File)}.
*/
private final File home;
/**
* Pass-thru method to {@link #DirconfFeature(StandardProject, File)} that
* sets the profile directory to be conf/profile.
*
* @param parent
*/
public DirconfFeature(StandardProject parent) {
this(parent, parent.getLayout().getConfDir());
}
/**
* This method must be called if configuration with properties files is to
* be supported. Broadly speaking, setting interpolated to true means that
* property placeholders of the form ${var} in config files will get
* replaced with their real values. This is useful if you use libraries that
* can't handle property placeholders at runtime.
*
* @param parent
* the owning parent
* @param home
* the parent directory of the profile, often used to contain
* more than one choice of profile directory
*/
public DirconfFeature(StandardProject parent, File home) {
this.parent = parent;
this.home = home;
this.workingDir = new File(parent.getLayout().getTargetDir(),
"dirconfig");
}
/**
* Temporary storage for configuration calculations.
*
* @return
*/
public File getWorkingDir() {
return workingDir;
}
/**
* Configures the parent config directory by overlaying the content of the
* specified profile directory, merging any properties files en route.
*
* When interpolate is specified to be true, all property references in all
* files (including the properties files themselves in the profile conf
* directory) are interpolated. This means every reference to ${var} is
* replaced with the value calculated from the properties files.
* Additionally the properties files are reduced to a single master
* properties file - see {@link ProjectLayout#getPropertiesFile()}.
*
* @param name
* the name of the profile directory itself
* @param interpolate
* true if ${var} placeholders should be replaced with their
* actual values
*/
public void configure(String name, boolean interpolate) {
File profileDir = getProfileDir(name);
getWorkingDir().mkdir();
ProjectLayout layout = parent.getLayout();
FileUtils.deleteDirectory(getWorkingDir());
if (interpolate) {
// merge the conf properties files to the working directory
reducePropertiesToWorkingDir(profileDir);
} else {
// merge the conf properties files to the working directory
mergePropertiesToWorkingDir(profileDir);
}
// copy the non conf properties files to the working directory
copyDir(profileDir, getWorkingDir(), null, "conf/*.properties",
interpolate);
// wipe over the parent directory with the working directory
copyDir(getWorkingDir(), layout.getRootDir(), null, null, false);
}
/**
* Removes all files in the parent directory that match the paths in the
* specified profile name. No state is saved for the original configure, so
* this is probably the best that can be done.
*
* @param name
* the name of the profile directory itself
*/
public void clean(String name) {
File profileDir = getProfileDir(name);
ProjectLayout layout = parent.getLayout();
File projectRootDir = layout.getRootDir();
// get all the resource under the profile root directory
final List profileResources = new ArrayList();
listFiles(profileDir, profileResources);
// delete the matching resource under the parent directory
for (File profileResource : profileResources) {
String relativePath = getRelativePath(profileDir, profileResource);
File projectResource = new File(projectRootDir, relativePath);
projectResource.delete();
}
// delete the special 'all properties' file just in case interpolated
layout.getPropertiesFile().delete();
}
/**
* The profile directory which is the `name` subdirectory under the home
* directory.
*
* @param name
* @return
*/
private File getProfileDir(String name) {
return new File(home, name);
}
/**
* Recursively obtains all the files and not directories under the specified
* directory argument. They are added to the profileResources list.
*
* @param directory
* @param profileResources
*/
private void listFiles(final File directory,
final List profileResources) {
directory.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
listFiles(pathname, profileResources);
} else {
profileResources.add(pathname);
}
return false;
}
});
}
/**
* Copies properties files from the profileInstanceDir directory to the
* working directory merging into one big properties file. Also merges with
* and overrides the big properties file in the parent conf if any.
*
* @param profileInstanceDir
*/
private void reducePropertiesToWorkingDir(File profileInstanceDir) {
// combine all the properties files from the profile instance directory
CompositeConfiguration comp = new CompositeConfiguration();
List profileFiles = getConfProperties(profileInstanceDir);
for (File profileFile : profileFiles) {
comp.addConfiguration(new PropertiesConfiguration(profileFile));
}
// next add the existing single properties file from parent config
// so it is last in the order of precedence
File propertiesFile = parent.getLayout().getPropertiesFile();
if (propertiesFile.exists()) {
PropertiesConfiguration conf = new PropertiesConfiguration(
propertiesFile);
comp.addConfiguration(conf);
}
// finally save the interpolated properties to the working directory to
// a single file with the same name as from the parent conf directory
PropertiesConfiguration workingConfig = new PropertiesConfiguration(
getWorkingPropertiesFile());
workingConfig.copy(comp.interpolatedConfiguration());
workingConfig.save();
}
/**
* Calculates the path of the single parent properties file when in the
* working directory.
*
* @return
*/
private File getWorkingPropertiesFile() {
ProjectLayout layout = parent.getLayout();
String workingPropertiesName = layout.getPropertiesFile().getName();
String confDirName = layout.getConfDir().getName();
File workingConfDir = new File(getWorkingDir(), confDirName);
File workingPropertiesFile = new File(workingConfDir,
workingPropertiesName);
return workingPropertiesFile;
}
/**
* All the properties from the profile instance conf directory need to
* override their equivalents in the actual parent conf directory. This is
* done by combining each pair in memory and writing to the working
* directory.
*
* @param profileInstanceDir
*/
private void mergePropertiesToWorkingDir(File profileInstanceDir) {
List profileFiles = getConfProperties(profileInstanceDir);
for (File profileFile : profileFiles) {
String relativePath = getRelativePath(profileInstanceDir,
profileFile);
File projectFile = new File(parent.getLayout().getRootDir(),
relativePath);
mergeFilesToWorkingDir(profileFile, projectFile);
}
}
/**
* Merges the properties from the profile file with those from the parent
* file into the working directory.
*
* @param profileFile
* @param projectFile
*/
private void mergeFilesToWorkingDir(File profileFile, File projectFile) {
File rootDir = parent.getLayout().getRootDir();
String relativePath = getRelativePath(rootDir, projectFile);
if (projectFile.exists()) {
PropertiesConfiguration profileProps = new PropertiesConfiguration(
profileFile);
PropertiesConfiguration projectProps = new PropertiesConfiguration(
projectFile);
CompositeConfiguration mergedProps = new CompositeConfiguration();
mergedProps.addConfiguration(profileProps);
mergedProps.addConfiguration(projectProps);
File workingFile = new File(getWorkingDir(), relativePath);
PropertiesConfiguration workingProps = new PropertiesConfiguration(
workingFile);
workingProps.copy(mergedProps);
workingProps.save();
} else {
File destFile = new File(getWorkingDir(), relativePath);
AntTarget target = new AntTarget("configure");
Copy copy = new Copy();
copy.setTaskName("copy-property-file");
target.addTask(copy);
copy.setFile(profileFile);
copy.setTofile(destFile);
copy.setOverwrite(false);
target.execute();
}
}
/**
* Returns a list of the properties files in the conf directory of the
* specified parent directory, if any.
*
* @param parentDir
* @return
*/
private List getConfProperties(File parentDir) {
final List confProperties = new ArrayList();
ProjectLayout layout = parent.getLayout();
File confDir = new File(parentDir, layout.getConfDir().getName());
confDir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
boolean isPropertyFile = pathname.getName().endsWith(
".properties");
if (isPropertyFile) {
confProperties.add(pathname);
}
return false;
}
});
return confProperties;
}
/**
* Calculates the relative path string of the child to the parent. As an
* example for a parent path of /usr/dev/parent and a child path of
* /usr/dev/parent/foo/bar, the relative path is foo/bar.
*
* @param parent
* @param child
* @return
*/
private String getRelativePath(File parent, File child) {
int relativePathPos = parent.getAbsolutePath().length();
return child.getAbsolutePath().substring(relativePathPos);
}
/**
* Copies the src directory to the dest directory, overwriting any existing
* files. The includes and excludes ant patterns can also be specified,
* although they can be set to null if no filtering is required.
*
* @param src
* the source directory
* @param dest
* the destination directory
* @param includes
* an ant pattern used to filter files that should be copied
* @param excludes
* an ant pattern used to filter files that should not be copied
* @param resolveProperties
* true if ${var} placeholders should be replaced with their
* property file values, false otherwise
*/
private void copyDir(File src, File dest, String includes, String excludes,
boolean resolveProperties) {
AntTarget target = new AntTarget("configure");
target.initLogging(Project.MSG_INFO);
Copy copy = new Copy();
target.addTask(copy);
copy.setTaskName("copy-dir");
FileSet fileSet = new FileSet();
fileSet.setDir(src);
if (includes != null) {
fileSet.setIncludes(includes);
}
if (excludes != null) {
fileSet.setExcludes(excludes);
}
copy.addFileset(fileSet);
copy.setTodir(dest);
copy.setOverwrite(true);
if (resolveProperties) {
FilterSet filterSet = copy.createFilterSet();
filterSet.setBeginToken("${");
filterSet.setEndToken("}");
FiltersFile filtersFile = filterSet.createFiltersfile();
filtersFile.setFile(getWorkingPropertiesFile());
}
target.execute();
}
}