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);
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
133 throw new RuntimeException(e);
134
135 } catch (final SAXException e) {
136
137
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 }