Re: New plugin - regular expression based properties
"Ing. Daniel TIHELKA Ph.D." <[email protected]> Fri, 20 Apr 2012 13:18:45 +0200
| Newsgroups | gmane.comp.java.cruise-control.devel |
|---|---|
| Message-ID | <58c7-4f914600-fb-47c5ca00@71600694> |
Here they are. Dan T. On Friday, April 20, 2012 12:36 CEST, Julian Simpson <[email protected]> wrote: > On 19 April 2012 22:30, Daniel Tihelka <[email protected]> wrote: > > > ** > > > > Hallo, > > > > I have just committed change which allows to write 3rd party properties > > plugin (e.g. getting properties from a database). > > > > > > I also have an implementation of "regular expression properties plugin" > > which parses a given text file and defines properties based on the regular > > expressions. > > > > > > [snip] > > > > > > Now, the question is: do you want to have this regexproperties plugin > > included in CC? I can manage it as 3rd party plugin, but I thing it may > > be quite useful for other uses as well. > > > > > I think it's worth looking at - where can we see the source? > > Best > > Julian. > > > > > Best regards, > > > > Dan T. > > > > > > > > > > ------------------------------------------------------------------------------ > > For Developers, A Lot Can Happen In A Second. > > Boundary is the first to Know...and Tell You. > > Monitor Your Applications in Ultra-Fine Resolution. Try it FREE! > > http://p.sf.net/sfu/Boundary-d2dvs2 > > _______________________________________________ > > Cruisecontrol-devel mailing list > > [email protected] > > https://lists.sourceforge.net/lists/listinfo/cruisecontrol-devel > > > > > > > -- > Julian Simpson > The Build Doctor Ltd. > http://www.build-doctor.com > [email protected] > (+44) 207 183 0323 ------------------------------------------------------------------------------ For Developers, A Lot Can Happen In A Second. Boundary is the first to Know...and Tell You. Monitor Your Applications in Ultra-Fine Resolution. Try it FREE! http://p.sf.net/sfu/Boundary-d2dvs2 _______________________________________________ Cruisecontrol-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/cruisecontrol-devel
RegexProperties.java
(text/x-java, 10.9 KB)
/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2007, ThoughtWorks, Inc.
* 200 E. Randolph, 25th Floor
* Chicago, IL 60601 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************/
package zcu.kky;
import net.sourceforge.cruisecontrol.config.FileResolver;
import net.sourceforge.cruisecontrol.config.PropertiesPlugin;
import net.sourceforge.cruisecontrol.config.XmlResolver;
import net.sourceforge.cruisecontrol.gendoc.annotations.SkipDoc;
import net.sourceforge.cruisecontrol.util.Util;
import net.sourceforge.cruisecontrol.util.ValidationHelper;
import net.sourceforge.cruisecontrol.ProjectXMLHelper;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.ResolverUser;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* <p>The <i>regular expression property</i> plugin is used to set a property (or a set of
* properties) from such lines in a general text file (with unspecified format) which matches
* the given regular expression . The properties set through this plugin behave equally to
* the properties set by the <code><property></code> element.
*
* To define a property from a file with unspecified format, the following attributes must be
* set:
* <ol>
* <li>The name of file to parse.</li>
* <li>Regular expression against which lines in the file are matched. The regular expression
* should contain at least 2 groups, one standing for the name of property, one standing
* for its value.</li>
* <li>The definition of the property name template - it may be as simple as just the reference
* to the first group, however, the name may be adjusted, if necessary.</li>
* <li>The definition of the property value template, following the same principle as for the
* property name.</li>
* </ol>
*
* As an example, let us consider the file with lines as follows:
* <pre>
* name1[value1]
* name2[value2] Anything may follow ...
*
* there may be anything as well ...
*
* name3[value3]; name4[value4]; name5[value5]
* </pre>
*
* and we want to define properties based on this. Moreover, we want to alter the names into
* the form: <code>build.attrib.special_name_1</code>. To achieve this, we need to define regex
* pattern:
* <pre>
* \s*([a-zA-Z]+)(\d+)[(\S+)]
* </pre>
* then, the template of property name must be set to (referring to groups 1 and 2):
* <pre>
* build.attrib.special_\1_\2
* </pre>
* and the value of property value is simple reference to group 3:
* <pre>
* \3
* </pre>
*
* In CruiseControl project, the properties such defined are accessible by standard way, i.e.
* <code>${build.attrib.special_name_1}</code>, <code>${build.attrib.special_name_2}</code>,
* etc.
*/
public class RegexProperties implements PropertiesPlugin, ResolverUser {
/** The attribute set by {@link #setFile(String)} */
private String file = null;
/** The attribute set by {@link #setPattern(String)} */
private String matchPattern = null;
/** The attribute set by {@link #setName(String)} */
private String nameTemplate = null;
/** The attribute set by {@link #setValue(String)} */
private String valueTemplate = null;
/** The guard of properties file changes, set by #setFileResolver(FileResolver) */
private FileResolver fileResolver;
/**
* Sets the instance of {@link FileResolver}. As it is claimed in
* {@link ResolverUser#setFileResolver(FileResolver)} documentation, it must be ensured
* that this method is called earlier than the other methods using the file resolver.
*
* @param resolver the instance to fill;
*/
@SkipDoc
@Override
public void setFileResolver(final FileResolver resolver) {
fileResolver = resolver;
}
/**
* The implementation of {@link ResolverUser#setXmlResolver(XmlResolver)}. Since XML
* resolver is not required here, it ignores the call.
*/
@SkipDoc
@Override
public void setXmlResolver(XmlResolver arg0) {
// Not needed ...
}
/**
* Called after the configuration is read to make sure that all the mandatory parameters were
* specified and have correct values.
*
* @throws CruiseControlException if there was a configuration error.
*/
public void validate() throws CruiseControlException {
/* Are the required attributes set? */
ValidationHelper.assertIsSet(file, "file", this.getClass());
ValidationHelper.assertIsSet(matchPattern, "pattern", this.getClass());
ValidationHelper.assertIsSet(nameTemplate, "name", this.getClass());
ValidationHelper.assertIsSet(valueTemplate, "value", this.getClass());
/* The file must exist */
ValidationHelper.assertExists(new File(this.file), "file", this.getClass());
/* Check, if the pattern is correct */
try {
Pattern.compile(matchPattern);
} catch (PatternSyntaxException e) {
ValidationHelper.fail("pattern '" + matchPattern + "' is not valid", e);
}
} // validate
/**
* Called to parse the file and define the properties.
*
* @param props the map into which to set the properties parsed.
* @param failIfMissing if to fail when .... ????
* @throws CruiseControlException if there was an critical error
*/
@Override
public void loadProperties(final Map<String, String> props, final boolean failIfMissing)
throws CruiseControlException {
final BufferedReader reader;
final Pattern pattern;
// TODO FIXME: how to handle failIfMissing attribute?
try {
reader = new BufferedReader(new InputStreamReader(fileResolver.getInputStream(this.file)));
pattern = Pattern.compile(this.matchPattern);
try {
/* Read the theFile line by line and match them against the pattern. Expand macros
* as we go. We must do this manually to preserve the order of the properties. */
String line;
while ((line = reader.readLine()) != null) {
final Matcher matcher = pattern.matcher(line);
/* Find all matches at the line ... */
while (matcher.find()) {
String parsedName = this.nameTemplate;
String parsedValue = this.valueTemplate;
/* Replace group references in the templates by the values */
for (int i = 1; i <= matcher.groupCount(); i++) {
parsedName = parsedName.replace("\\" + Integer.toString(i), matcher.group(i));
parsedValue = parsedValue.replace("\\" + Integer.toString(i), matcher.group(i));
}
/* Replace ${....} definitions by values from properties already read */
parsedName = Util.parsePropertiesInString(props, parsedName, failIfMissing);
parsedValue = Util.parsePropertiesInString(props, parsedValue, failIfMissing);
/* Set the parsed property */
ProjectXMLHelper.setProperty(props, parsedName, parsedValue);
}
}
} finally {
reader.close();
}
} catch (IOException e) {
throw new CruiseControlException("Could not parse properties from the file \"" + this.file
+ "\".", e);
}
} // loadProperties
/**
* Sets the name of file to read and parse.
* @param file the name of file to read and parse.
* @required Yes
*/
public void setFile(String file) {
this.file = file;
} // setFile
/**
* Sets the pattern against which the lines read are matched.
* @param pattern the regular expression pattern of interest.
* @required Yes.
*/
public void setPattern(String pattern) {
this.matchPattern = pattern;
} // setPattern
/**
* Sets the template of property name, used in conjunction with <code>pattern</code>.
* @param name the template from which the property name is created (used to refer to at
* least one group the the pattern set by {@link #setPattern(String)})
* @required Yes.
*/
public void setName(String name) {
this.nameTemplate = name;
} // setName
/**
* Sets the template of property value, used in conjunction with <code>pattern</code>.
* @param value the template from which the property value is created (used to refer to at
* least one group the the pattern set by {@link #setPattern(String)})
* @required Yes.
*/
public void setValue(String value) {
this.valueTemplate = value;
} // setValue
}
RegexPropertiesTest.java
(text/x-java, 16 KB)
/********************************************************************************
*
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2003, ThoughtWorks, Inc.
* 200 E. Randolph, 25th Floor
* Chicago, IL 60601 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
********************************************************************************/
package zcu.kky;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import junit.framework.TestCase;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.config.FileResolver;
import net.sourceforge.cruisecontrol.config.XMLConfigManager;
import net.sourceforge.cruisecontrol.testutil.TestUtil.FilesToDelete;
import net.sourceforge.cruisecontrol.util.IO;
/**
* The test case for {@link RegexProperties} class.
*/
public final class RegexPropertiesTest extends TestCase
{
/** The properties to parse - the reference Map which must be achieved. */
private final Map<String, String> propsRefer;
/** The pattern of properties, see {@link RegexProperties#setPattern(String)}.
* - the name is a sequence of letters followed by a sequence of numbers
* - the value is anything except [ and ] characters */
private final String propPattern = "([a-zA-Z]+\\d+)\\[([^\\[\\]]*)\\]";
/** The template of property name, see {@link RegexProperties#setName(String)}. */
private final String propNameTempl = "\\1";
/** The template of property value, see {@link RegexProperties#setValue(String)}. */
private final String propValTempl = "\\2";
/** The list of files created during the test - they are deleted by {@link #tearDown()}
* method ... */
private final FilesToDelete filesToDel;
/** String which will be replaced by a property in the defined format */
private final String PROP_HERE = "XXXXXXXXX";
/**
* Constructor.
*/
public RegexPropertiesTest() {
filesToDel = new FilesToDelete();
propsRefer = new HashMap<String, String>();
/* Fill the reference properties */
propsRefer.put("name1", "value1");
propsRefer.put("name2", "the second value");
propsRefer.put("name3", "value/third");
propsRefer.put("name4", "");
propsRefer.put("name5", "qwertyuiop1234567890");
}
/**
* Setup test environment.
*/
@Override
protected void setUp() throws Exception {
super.setUp();
}
/**
* Clears test environment.
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
filesToDel.delete();
}
/**
* Checks the validation when file to parse is not set (it is required item).
*/
public void testValidate_noFile() {
final RegexProperties propsParser = new RegexProperties();
/* setFile() is not called ... */
propsParser.setPattern(propPattern);
propsParser.setName(propNameTempl);
propsParser.setValue(propValTempl);
/* Must not be validated */
try {
propsParser.validate();
fail("RegexProperties should throw an exception when required 'file' attribute is not set.");
} catch (CruiseControlException e) {
assertEquals("exception message when the required file attribute is not set",
"'file' is required for RegexProperties", e.getMessage());
}
}
/**
* Checks the validation when regular expression pattern is not set (it is required item).
* @throws IOException
*/
public void testValidate_noPattern() throws IOException {
final RegexProperties propsParser = new RegexProperties();
/* does not matter that the file is empty */
propsParser.setFile(getFile("foo", ".txt").getAbsolutePath());
/* setPattern() is not called ... */
propsParser.setName(propNameTempl);
propsParser.setValue(propValTempl);
/* Must not be validated */
try {
propsParser.validate();
fail("RegexProperties should throw an exception when required 'pattern' attribute is not set.");
} catch (CruiseControlException e) {
assertEquals("exception message when the required pattern attribute is not set",
"'pattern' is required for RegexProperties", e.getMessage());
}
}
/**
* Checks the validation when regular expression pattern is invalid.
* @throws IOException
*/
public void testValidate_badPattern() throws IOException {
final RegexProperties propsParser = new RegexProperties();
final String invalidPattern = "([a-zA-Z]+\\d+\\[(\\S*)\\]"; /* one closing group bracket is
missing. */
/* does not matter that the file is empty */
propsParser.setFile(getFile("foo", ".txt").getAbsolutePath());
propsParser.setPattern(invalidPattern);
propsParser.setName(propNameTempl);
propsParser.setValue(propValTempl);
/* Must not be validated */
try {
propsParser.validate();
fail("RegexProperties should throw an exception when required 'pattern' attribute is not set.");
} catch (CruiseControlException e) {
assertEquals("pattern '" + invalidPattern + "' is not valid", e.getMessage());
}
}
/**
* Checks the validation when name template is not set (it is required item).
* @throws IOException
*/
public void testValidate_noName() throws IOException {
final RegexProperties propsParser = new RegexProperties();
/* does not matter that the file is empty */
propsParser.setFile(getFile("foo", ".txt").getAbsolutePath());
propsParser.setPattern(propPattern);
/* setName() is not called ... */
propsParser.setValue(propValTempl);
/* Must not be validated */
try {
propsParser.validate();
fail("RegexProperties should throw an exception when required 'pattern' attribute is not set.");
} catch (CruiseControlException e) {
assertEquals("exception message when the required name attribute is not set",
"'name' is required for RegexProperties", e.getMessage());
}
}
/**
* Checks the validation when value template is not set (it is required item).
* @throws IOException
*/
public void testValidate_noValue() throws IOException {
final RegexProperties propsParser = new RegexProperties();
/* does not matter that the file is empty */
propsParser.setFile(getFile("foo", ".txt").getAbsolutePath());
propsParser.setPattern(propPattern);
propsParser.setName(propNameTempl);
/* setValue() is not called ... */
/* Must not be validated */
try {
propsParser.validate();
fail("RegexProperties should throw an exception when required 'pattern' attribute is not set.");
} catch (CruiseControlException e) {
assertEquals("exception message when the required value attribute is not set",
"'value' is required for RegexProperties", e.getMessage());
}
}
/**
* Checks the correct function of the properties parsing. Each property is stored on its own
* line.
*
* @throws CruiseControlException if the parsing fails!
* @throws IOException if the file from the properties are parsed cannot be created.
*/
public void testLoadProperties_correctSingleLine() throws CruiseControlException, IOException {
final File file = getFile("foo", ".txt");
final RegexProperties propsParser = new RegexProperties();
final Map<String, String> propsRead = new HashMap<String, String>();
/* Create the content of the file, 'XXXXXXX' strings will be replaced by the properties */
final String[] fileTemplate = {"sdffsdfgsf s gsgsfhs fhgsfh dsfadf",
"dsffad s dgf sdgstrsd",
PROP_HERE,
"dsf d f ds g dsgf sg s fgsfdgdf zd",
"",
"",
PROP_HERE + " dsf sdfsadfasdf<sdf df",
PROP_HERE,
"",
"dfd fdfadsfa dsfasdfasf",
" " + PROP_HERE,
"dsf " + PROP_HERE + "dsfasdf fzf ",
"f s fgsg sfgsfhgsgfh dfad fdfadf d",
};
// Write properties to file
writeFile(file, fileTemplate);
/* Fill the properties options */
propsParser.setFileResolver(new FileResolver.DummyResolver());
propsParser.setFile(file.getAbsolutePath());
propsParser.setPattern(propPattern);
propsParser.setName(propNameTempl);
propsParser.setValue(propValTempl);
/* Validate and run */
propsParser.validate();
propsParser.loadProperties(propsRead, false);
/* Check properties read to those required */
assertEquals(propsRefer, propsRead);
}
/**
* Checks the correct function of the properties parsing. There are several properties stored
* on one line.
*
* @throws CruiseControlException
* @throws IOException
*/
public void testLoadProperties_correctMultiLine() throws CruiseControlException, IOException {
final File file = getFile("foo", ".txt");
final RegexProperties propsParser = new RegexProperties();
final Map<String, String> propsRead = new HashMap<String, String>();
/* Create the content of the file, 'XXXXXXX' strings will be replaced by the properties */
final String[] fileTemplate = {"sdffsdfgsf s gsgsfhs dsfadf g",
PROP_HERE + " dsfsd " + PROP_HERE,
"dfd fdfadsfa dsfasdfd fdgadfgdf",
"f s fgsg sfgsfhgsgfhdf fdfadf d",
PROP_HERE + PROP_HERE + PROP_HERE,
"sdsdf adsf adsfasdf fadsfasdasd",
};
// Write properties to file
writeFile(file, fileTemplate);
/* Fill the properties options */
propsParser.setFileResolver(new FileResolver.DummyResolver());
propsParser.setFile(file.getAbsolutePath());
propsParser.setPattern(propPattern);
propsParser.setName(propNameTempl);
propsParser.setValue(propValTempl);
/* Validate and run */
propsParser.validate();
propsParser.loadProperties(propsRead, false);
/* Check properties read to those required */
assertEquals(propsRefer, propsRead);
}
/**
* Checks if the properties are reloaded when the content of file is changed.
* @throws CruiseControlException
* @throws IOException
*/
public void testShouldDetectChangesToPropertyFile() throws CruiseControlException, IOException {
// properties file
File propertyFile = getFile("foo", ".txt");
writeFile(propertyFile, new String[]{PROP_HERE + " some other string",
PROP_HERE + " another string" });
// project file
File projectFile = getFile("config", ".xml");
IO.write(projectFile, "<cruisecontrol>"
+ " <plugin name='regexproperties' classname='" + RegexProperties.class.getName() + "' />"
+ " <project name='DOESNTMATTER'>"
+ " <regexproperties file='" + propertyFile.getName() + "'"
+ " pattern='" + propPattern + "'"
+ " name='" + propNameTempl + "' value='" + propValTempl + "' />"
+ " <schedule> <ant/> </schedule>"
+ " </project>"
+ "</cruisecontrol>");
// Must create the whole config
XMLConfigManager config = new XMLConfigManager(projectFile);
assertFalse(config.reloadIfNecessary());
// Change the properties file
writeFile(propertyFile, new String[]{PROP_HERE + " " + PROP_HERE});
assertTrue(config.reloadIfNecessary());
assertFalse(config.reloadIfNecessary());
}
/**
* From the template of file to parse and the properties defined in {@link #propsRefer}
* creates a real file. Each occurrence of {@link #PROP_HERE} in the file template is
* replaced by one property from {@link #propsRefer}.
*
* The properties are stored in the form: <code>name[value]</code> (see {@link #propPattern}).
*
* @param file the file to write data into.
* @param fileTemplate the array of strings, each item gives one line.
* @throws CruiseControlException
*/
private void writeFile(final File file, final String[] fileTemplate) throws CruiseControlException
{
final StringBuffer fileData = new StringBuffer();
final Iterator<String> propNames = this.propsRefer.keySet().iterator();
/* Create the content of file according to the template */
for (String line : fileTemplate) {
while (line.indexOf(PROP_HERE) >= 0 && propNames.hasNext()) {
String name = propNames.next();
line = line.replaceFirst(PROP_HERE, name + "[" + propsRefer.get(name) + "]");
}
fileData.append(line);
fileData.append('\n');
}
/* write to file */
IO.write(file, fileData.toString());
}
/**
* Generates temporary file and stores it into {@link #files} array. The file is deleted
* by {@link #tearDown()} method.
*
* @throws IOException when the file cannot be created
* @todo move to FilesToDelete
*/
private File getFile(String prefix, String suffix) throws IOException {
File file;
// create the file and register it for deletition
filesToDel.add(file = File.createTempFile(prefix, suffix));
return file;
}
}