Re: Creating custom propertis plugin
Daniel Tihelka <[email protected]> Tue, 22 Nov 2011 08:28:57 +0100
| Newsgroups | gmane.comp.java.cruise-control.devel |
|---|---|
| Message-ID | <[email protected]> |
Hallo, it seems that I made some progress with the possibility to write 3rd party custom properties plugins. I have attached props_plugin_01.patch which contains changes necessary for correct registration of classes implementing PropertiesPlugin interface. I have made few tests and it seems to be working. Also, if you are interested, I have attached the second patch containing the same changes as the first one, plus RegexProperties class (and a test for it) which I plan to use for our builds. It's up to you, if you want to have this class officially in CC sources - if so, I will write documentation for it, if not, I am OK keeping it in our 3rd party CC extensions library. So, I would like to ask any of you to have a look at the proposed changes (especially those in patch 01). I tried the existing unit tests and nothing seems to be broken, but I would like to have your acknowledge before pushing the patch in. Thank you very much. Best regards, Dan T. (CC committer) ------------------------------------------------------------------------------ All the data continuously generated in your IT infrastructure contains a definitive record of customers, application performance, security threats, fraudulent activity, and more. Splunk takes this data and makes sense of it. IT sense. And common sense. http://p.sf.net/sfu/splunk-novd2d _______________________________________________ Cruisecontrol-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/cruisecontrol-devel
props_plugin_01.patch
(text/x-patch, 2.3 KB)
Index: src/net/sourceforge/cruisecontrol/CruiseControlConfig.java
===================================================================
--- src/net/sourceforge/cruisecontrol/CruiseControlConfig.java (revision 4601)
+++ src/net/sourceforge/cruisecontrol/CruiseControlConfig.java (working copy)
@@ -408,6 +408,18 @@
ProjectXMLHelper.registerProperty(nonFullyResolvedProjectProperties, propertyElement,
resolvers, FAIL_UPON_MISSING_PROPERTY);
}
+ // And custom properties plugins
+ for (final Object o : projectElement.getChildren()) {
+ final Element childElement = (Element) o;
+ final String nodeName = childElement.getName();
+ if (KNOWN_ROOT_CHILD_NAMES.contains(nodeName)) {
+ continue;
+ }
+ if (isCustomPropertiesPlugin(nodeName)) {
+ ProjectXMLHelper.registerCustomProperty(nonFullyResolvedProjectProperties, childElement,
+ resolvers, FAIL_UPON_MISSING_PROPERTY, PluginRegistry.createRegistry(rootPlugins));
+ }
+ }
// add the resolved rootProperties to the project's properties
final Map<String, String> thisProperties = nonFullyResolvedProjectProperties.thisMap;
Index: src/net/sourceforge/cruisecontrol/ProjectConfig.java
===================================================================
--- src/net/sourceforge/cruisecontrol/ProjectConfig.java (revision 4601)
+++ src/net/sourceforge/cruisecontrol/ProjectConfig.java (working copy)
@@ -49,6 +49,7 @@
import net.sourceforge.cruisecontrol.config.DefaultPropertiesPlugin;
import net.sourceforge.cruisecontrol.config.PluginPlugin;
+import net.sourceforge.cruisecontrol.config.PropertiesPlugin;
import net.sourceforge.cruisecontrol.gendoc.annotations.Cardinality;
import net.sourceforge.cruisecontrol.gendoc.annotations.Default;
import net.sourceforge.cruisecontrol.gendoc.annotations.Optional;
@@ -156,6 +157,11 @@
this.forceBuildNewProject = forceBuildNewProject;
}
+ @Description("Registers a general plug-in inherrited from PropertiesPlugin interface.")
+ public void add(PropertiesPlugin plugin) {
+ // Must be empty, plugin is registered somewhere else
+ }
+
/**
* @deprecated exists only for gendoc, should not be called.
*/
props_plugin_02.patch
(text/x-patch, 30 KB)
Index: test/net/sourceforge/cruisecontrol/config/RegexPropertiesTest.java
===================================================================
--- test/net/sourceforge/cruisecontrol/config/RegexPropertiesTest.java (revision 0)
+++ test/net/sourceforge/cruisecontrol/config/RegexPropertiesTest.java (revision 0)
@@ -0,0 +1,389 @@
+/********************************************************************************
+ *
+ * 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 net.sourceforge.cruisecontrol.config;
+
+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;
+ }
+
+
+}
Index: src/net/sourceforge/cruisecontrol/CruiseControlConfig.java
===================================================================
--- src/net/sourceforge/cruisecontrol/CruiseControlConfig.java (revision 4601)
+++ src/net/sourceforge/cruisecontrol/CruiseControlConfig.java (working copy)
@@ -408,6 +408,18 @@
ProjectXMLHelper.registerProperty(nonFullyResolvedProjectProperties, propertyElement,
resolvers, FAIL_UPON_MISSING_PROPERTY);
}
+ // And custom properties plugins
+ for (final Object o : projectElement.getChildren()) {
+ final Element childElement = (Element) o;
+ final String nodeName = childElement.getName();
+ if (KNOWN_ROOT_CHILD_NAMES.contains(nodeName)) {
+ continue;
+ }
+ if (isCustomPropertiesPlugin(nodeName)) {
+ ProjectXMLHelper.registerCustomProperty(nonFullyResolvedProjectProperties, childElement,
+ resolvers, FAIL_UPON_MISSING_PROPERTY, PluginRegistry.createRegistry(rootPlugins));
+ }
+ }
// add the resolved rootProperties to the project's properties
final Map<String, String> thisProperties = nonFullyResolvedProjectProperties.thisMap;
Index: src/net/sourceforge/cruisecontrol/config/RegexProperties.java
===================================================================
--- src/net/sourceforge/cruisecontrol/config/RegexProperties.java (revision 0)
+++ src/net/sourceforge/cruisecontrol/config/RegexProperties.java (revision 0)
@@ -0,0 +1,253 @@
+/********************************************************************************
+ * 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 net.sourceforge.cruisecontrol.config;
+
+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 name 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
+}
Index: src/net/sourceforge/cruisecontrol/ProjectConfig.java
===================================================================
--- src/net/sourceforge/cruisecontrol/ProjectConfig.java (revision 4601)
+++ src/net/sourceforge/cruisecontrol/ProjectConfig.java (working copy)
@@ -49,6 +49,7 @@
import net.sourceforge.cruisecontrol.config.DefaultPropertiesPlugin;
import net.sourceforge.cruisecontrol.config.PluginPlugin;
+import net.sourceforge.cruisecontrol.config.PropertiesPlugin;
import net.sourceforge.cruisecontrol.gendoc.annotations.Cardinality;
import net.sourceforge.cruisecontrol.gendoc.annotations.Default;
import net.sourceforge.cruisecontrol.gendoc.annotations.Optional;
@@ -156,6 +157,11 @@
this.forceBuildNewProject = forceBuildNewProject;
}
+ @Description("Registers a general plug-in inherrited from PropertiesPlugin interface.")
+ public void add(PropertiesPlugin plugin) {
+ // Must be empty, plugin is registered somewhere else
+ }
+
/**
* @deprecated exists only for gendoc, should not be called.
*/
signature.asc
(application/pgp-signature, 198 B)
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.17 (GNU/Linux) iEYEABECAAYFAk7LTz0ACgkQuYlVhxo/P1H2tACgiq/BY+jPeHclqWSzLb6p/YPM DNkAn2kCjj/JiZVDDfYjsP/Rap1tWBnN =08k5 -----END PGP SIGNATURE-----