View Javadoc

1   /***
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * For more information visit
11   *         http://72miles.com and
12   *         http://architecturerules.googlecode.com/svn/docs/index.html
13   */
14  
15  package com.seventytwomiles.architecturerules.configuration.xml;
16  
17  
18  import com.seventytwomiles.architecturerules.configuration.AbstractConfigurationFactory;
19  import com.seventytwomiles.architecturerules.domain.CyclicDependencyConfiguration;
20  import com.seventytwomiles.architecturerules.domain.Rule;
21  import com.seventytwomiles.architecturerules.domain.SourceDirectory;
22  import com.seventytwomiles.architecturerules.domain.SourcesConfiguration;
23  import com.seventytwomiles.architecturerules.exceptions.InvalidConfigurationException;
24  import org.apache.commons.digester.Digester;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.xml.sax.SAXException;
28  
29  import java.io.IOException;
30  import java.io.StringReader;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  
35  
36  /***
37   * <p>Apache Commons Digester implementation of the <code>ConfigurationFactory</code></p>
38   *
39   * @author mikenereson
40   * @see AbstractConfigurationFactory
41   */
42  public class DigesterConfigurationFactory extends AbstractConfigurationFactory {
43  
44  
45      protected static final Log log = LogFactory.getLog(
46              DigesterConfigurationFactory.class);
47  
48  
49      /***
50       * @todo remove this (it's useless)?
51       */
52      public DigesterConfigurationFactory() {
53  
54      }
55  
56  
57      /***
58       * <p>Instantiates a new <code>ConfigurationFactory</code> and processes the
59       * configuration found in the <code>File</code> with the given
60       * <tt>configurationFileName</tt>.</p>
61       *
62       * @param fileName name of the <code>File</code> in the
63       * classpath to load configuration from.
64       */
65      public DigesterConfigurationFactory(final String fileName) {
66  
67          final String configurationXml
68                  = getConfigurationAsXml(fileName);
69  
70          validateConfiguration(configurationXml);
71          processConfiguration(configurationXml);
72      }
73  
74  
75      /***
76       * <p>Validate the configurationXml.</p>
77       *
78       * @param configurationXml String xml content to validate
79       * @see "architecture-rules.dtd"
80       */
81      @Override
82      protected void validateConfiguration(final String configurationXml) {
83  
84          final Digester digester = new Digester();
85          digester.setValidating(false); // TODO: set to true to actually validate
86  
87          /***
88           * TODO: apply DTD to configuration then try digester.parse
89           */
90  
91          final StringReader configurationReader
92                  = new StringReader(configurationXml);
93  
94          try {
95  
96              digester.parse(configurationReader);
97  
98          } catch (final IOException e) {
99  
100             throw new InvalidConfigurationException(
101                     "configuration xml file contains invalid configuration properties",
102                     e);
103 
104         } catch (final SAXException e) {
105 
106             throw new InvalidConfigurationException(
107                     "configuration xml file contains invalid configuration properties",
108                     e);
109         }
110     }
111 
112 
113     /***
114      * <p>Read the configuration in the configuration l File to a
115      * <code>Configuration</code> entity.</p>
116      *
117      * <p>protected scope so that it could be individually tested.</p>
118      *
119      * @param configurationXml String of xml configuration
120      */
121     void processConfiguration(final String configurationXml) {
122 
123         try {
124 
125             processSources(configurationXml);
126             processRules(configurationXml);
127             processCyclicDependencyConfiguration(configurationXml);
128             processSourcesNotFoundConfiguration(configurationXml);
129 
130         } catch (final IOException e) {
131 
132             /* Can this be handled better? Should it be? */
133             throw new RuntimeException(e);
134 
135         } catch (final SAXException e) {
136 
137             /* Can this be handled better? Should it be? */
138             throw new RuntimeException(e);
139         }
140     }
141 
142 
143     /***
144      * <p>Read xml configuration for source directories into SourceDirectory
145      * instances.</p>
146      *
147      * <p>package scope so that it could be individually tested</p>
148      *
149      * @param xml String xml to parse
150      * @throws IOException when an input/output error occurs
151      * @throws SAXException when given xml can not be parsed
152      */
153     void processSources(final String xml)
154             throws IOException, SAXException {
155 
156         final Digester digester = getDigester();
157 
158         digester.addObjectCreate(XmlConfiguration.sources,
159                 ArrayList.class);
160 
161         digester.addObjectCreate(XmlConfiguration.source,
162                 SourceDirectory.class);
163 
164         digester.addCallMethod(XmlConfiguration.source, "setPath", 0);
165 
166         digester.addSetProperties(XmlConfiguration.source, "not-found",
167                 "notFound");
168 
169         digester.addSetNext(XmlConfiguration.source, "add");
170 
171         final StringReader reader = new StringReader(xml);
172 
173         final List<SourceDirectory> parsedSources
174                 = (ArrayList<SourceDirectory>) digester.parse(reader);
175 
176         sources.clear();
177 
178         for (final SourceDirectory sourceDirectory : parsedSources)
179             sources.add(sourceDirectory);
180     }
181 
182 
183     /***
184      * <p>Process XML configuration to read rules elements into
185      * <code>Rules</code></p>
186      *
187      * <p>package scope so that it could be individually tested</p>
188      *
189      * @param xml String xml to parse
190      * @throws IOException when an input/output error occurs
191      * @throws SAXException when given xml can not be parsed
192      */
193     void processRules(final String xml)
194             throws IOException, SAXException {
195 
196         final Digester digester = getDigester();
197 
198         digester.addObjectCreate(XmlConfiguration.rules, ArrayList.class);
199         digester.addObjectCreate(XmlConfiguration.rule, Rule.class);
200         digester.addSetProperties(XmlConfiguration.rule, "id", "idString");
201         digester.addCallMethod(XmlConfiguration.ruleComment, "setComment", 0);
202         digester.addCallMethod(XmlConfiguration.rulePackage, "addPackage", 0);
203 
204         digester.addCallMethod(XmlConfiguration.ruleViolation,
205                 "addViolation", 0);
206 
207         digester.addSetNext(XmlConfiguration.rule, "add");
208 
209         final StringReader reader = new StringReader(xml);
210         final List<Rule> parsedRules = (ArrayList<Rule>) digester.parse(reader);
211 
212         rules.clear();
213         rules.addAll(parsedRules);
214     }
215 
216 
217     /***
218      * <p>Process <tt>cyclicDependency</tt> element into
219      * <code>CyclicDependencyConfiguration</code> entity.</p>
220      *
221      * <p>protected scope so that it could be individually tested</p>
222      *
223      * @param configurationXml String xml to parse
224      * @throws IOException when an input/output error occurs
225      * @throws SAXException when given xml can not be parsed
226      */
227     void processCyclicDependencyConfiguration(final String configurationXml)
228             throws IOException, SAXException {
229 
230         final Digester digester = getDigester();
231 
232         final StringReader configurationReader
233                 = new StringReader(configurationXml);
234 
235         digester.addObjectCreate(
236                 XmlConfiguration.cyclicalDependency,
237                 CyclicDependencyConfiguration.class);
238 
239         digester.addSetProperties(
240                 XmlConfiguration.cyclicalDependency,
241                 "test", "test");
242 
243         CyclicDependencyConfiguration configuration;
244         configuration = (CyclicDependencyConfiguration) digester.parse(
245                 configurationReader);
246 
247         /***
248          * If no configuration was provided in the xml, then use the default
249          * values.
250          */
251         if (configuration == null)
252             configuration = new CyclicDependencyConfiguration();
253 
254         final String test = configuration.getTest();
255 
256         if (test.equalsIgnoreCase("true") || test.equalsIgnoreCase("false")) {
257 
258             doCyclicDependencyTest = Boolean.valueOf(test);
259 
260         } else {
261 
262             throw new InvalidConfigurationException("'" + test +
263                     "' is not a valid value for " +
264                     "cyclicalDependency configuration. " +
265                     "Use <cyclicalDependency test=\"true\"/> " +
266                     "or <cyclicalDependency test=\"false\"/>");
267         }
268     }
269 
270 
271     /***
272      * <p>Process XML sources <tt>not-found</tt> attribute to a
273      * <code>SourcesConfiguration</code> entity.</p>
274      *
275      * <p>package scope so that it could be individually tested</p>
276      *
277      * @param configurationXml String xml to parse
278      * @throws IOException when an input/output error occurs
279      * @throws SAXException when given xml can not be parsed
280      */
281     void processSourcesNotFoundConfiguration(final String configurationXml)
282             throws IOException, SAXException {
283 
284         final Digester digester = getDigester();
285 
286         final StringReader configurationReader
287                 = new StringReader(configurationXml);
288 
289         digester.addObjectCreate(
290                 XmlConfiguration.sources, SourcesConfiguration.class);
291 
292         digester.addSetProperties(
293                 XmlConfiguration.sources, "no-packages", "noPackages");
294 
295         SourcesConfiguration configuration
296                 = (SourcesConfiguration) digester.parse(configurationReader);
297 
298         /***
299          * If no configuration was provided in the xml, then use the default
300          * value.
301          */
302         if (configuration == null)
303             configuration = new SourcesConfiguration();
304 
305         final String value = configuration.getNoPackages();
306 
307         final boolean isIgnore = value.equalsIgnoreCase("ignore");
308         final boolean isException = value.equalsIgnoreCase("exception");
309 
310         if (isIgnore || isException) {
311 
312             throwExceptionWhenNoPackages = value.equalsIgnoreCase("exception");
313 
314         } else {
315 
316             throw new InvalidConfigurationException("'" + value +
317                     "' is not a valid value for the " +
318                     "sources no-packages configuration. " +
319                     "Use <sources no-packages=\"ignore\">, " +
320                     "<sources no-packages=\"exception\"> or " +
321                     "leave the property unset.");
322         }
323     }
324 
325 
326     /***
327      * <p>Configures a Digester</p>
328      *
329      * @return Digester
330      */
331     private Digester getDigester() {
332 
333         final SaxErrorHandler errorHandler = new SaxErrorHandler();
334 
335         final Digester digester = new Digester();
336         digester.setErrorHandler(errorHandler);
337 
338         return digester;
339     }
340 }