All Downloads are FREE. Search and download functionalities are using the official Maven repository.

protoj.lang.DirconfFeature Maven / Gradle / Ivy

The newest version!
/**
 * 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: * *

    *
  1. -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.
  2. *
* * @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(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy