1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.model.reader;
35
36 import info.magnolia.cms.util.ClasspathResourcesUtil;
37 import info.magnolia.module.ModuleManagementException;
38 import info.magnolia.module.model.ModuleDefinition;
39 import info.magnolia.module.model.Version;
40 import org.apache.commons.betwixt.io.BeanReader;
41 import org.apache.commons.io.IOUtils;
42 import org.jdom.DocType;
43 import org.jdom.Document;
44 import org.jdom.JDOMException;
45 import org.jdom.input.SAXBuilder;
46 import org.jdom.output.XMLOutputter;
47 import org.xml.sax.SAXException;
48 import org.xml.sax.SAXParseException;
49
50 import java.beans.IntrospectionException;
51 import java.io.IOException;
52 import java.io.InputStreamReader;
53 import java.io.Reader;
54 import java.io.StringReader;
55 import java.io.StringWriter;
56 import java.net.URL;
57 import java.util.HashMap;
58 import java.util.Map;
59 import java.util.regex.Matcher;
60 import java.util.regex.Pattern;
61
62
63
64
65
66
67
68
69 public class BetwixtModuleDefinitionReader implements ModuleDefinitionReader {
70 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BetwixtModuleDefinitionReader.class);
71
72 private static final String DTD = "/info/magnolia/module/model/module.dtd";
73
74 private final String dtdUrl;
75 private final BeanReader beanReader;
76
77 public BetwixtModuleDefinitionReader() {
78 final URL dtd = getClass().getResource(DTD);
79 if (dtd == null) {
80 throw new IllegalStateException("DTD not found at " + DTD);
81 }
82 dtdUrl = dtd.toString();
83
84 final BetwixtBindingStrategy bindingStrategy = new BetwixtBindingStrategy();
85 bindingStrategy.registerConverter(Version.class, new VersionConverter());
86
87 beanReader = new BeanReader();
88 try {
89 beanReader.getXMLIntrospector().getConfiguration().setTypeBindingStrategy(bindingStrategy);
90 beanReader.setValidating(true);
91 beanReader.setErrorHandler(new ErrorHandler());
92 beanReader.registerBeanClass(ModuleDefinition.class);
93 } catch (IntrospectionException e) {
94 throw new RuntimeException(e);
95 }
96 }
97
98 public Map<String, ModuleDefinition> readAll() throws ModuleManagementException {
99 final Map<String, ModuleDefinition> moduleDefinitions = new HashMap<String, ModuleDefinition>();
100
101 final String[] defResources = findModuleDescriptors();
102
103 for (final String resourcePath : defResources) {
104 log.debug("Parsing module file {}", resourcePath);
105 final ModuleDefinition def = readFromResource(resourcePath);
106 moduleDefinitions.put(def.getName(), def);
107 }
108 return moduleDefinitions;
109 }
110
111 protected String[] findModuleDescriptors() {
112 return ClasspathResourcesUtil.findResources(new ClasspathResourcesUtil.Filter() {
113 public boolean accept(String name) {
114 return name.startsWith("/META-INF/magnolia/") && name.endsWith(".xml");
115 }
116 });
117 }
118
119 public ModuleDefinition read(Reader in) throws ModuleManagementException {
120 try {
121 final Reader replacedDtd = replaceDtd(in);
122 return (ModuleDefinition) beanReader.parse(replacedDtd);
123 } catch (IOException e) {
124 throw new ModuleManagementException("Can't read module definition file: " + e.getMessage(), e);
125 } catch (SAXException e) {
126 throw new ModuleManagementException(e.getMessage(), e);
127 }
128 }
129
130 public ModuleDefinition readFromResource(String resourcePath) throws ModuleManagementException {
131 final InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream(resourcePath));
132 try {
133 return read(reader);
134 } finally {
135 try {
136 reader.close();
137 } catch (IOException e) {
138 log.error("Can't close input for " + resourcePath);
139 }
140 }
141 }
142
143
144
145
146
147
148
149
150 private Reader replaceDtd(Reader reader) throws IOException {
151 String content = IOUtils.toString(reader);
152
153
154 Pattern pattern = Pattern.compile("<!DOCTYPE .*>");
155 Matcher matcher = pattern.matcher(content);
156 content = matcher.replaceFirst("");
157
158
159 try {
160 Document doc = new SAXBuilder().build(new StringReader(content));
161 doc.setDocType(new DocType("module", dtdUrl));
162
163 XMLOutputter outputter = new XMLOutputter();
164 StringWriter writer = new StringWriter();
165 outputter.output(doc, writer);
166 final String replacedDtd = writer.toString();
167 return new StringReader(replacedDtd);
168 } catch (JDOMException e) {
169 throw new RuntimeException(e);
170 }
171 }
172
173 private static class ErrorHandler implements org.xml.sax.ErrorHandler {
174
175 public void warning(SAXParseException e) throws SAXException {
176 log.warn("Warning on module definition " + getSaxParseExceptionMessage(e));
177 }
178
179 public void error(SAXParseException e) throws SAXException {
180 throw new SAXException("Invalid module definition file, error " + getSaxParseExceptionMessage(e), e);
181 }
182
183 public void fatalError(SAXParseException e) throws SAXException {
184 throw new SAXException("Invalid module definition file, fatal error " + getSaxParseExceptionMessage(e), e);
185 }
186 }
187
188 private static String getSaxParseExceptionMessage(SAXParseException e) {
189 return "at line " + e.getLineNumber() + " column " + e.getColumnNumber() + ": " + e.getMessage();
190 }
191 }