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.InputStream;
53 import java.io.InputStreamReader;
54 import java.io.Reader;
55 import java.io.StringReader;
56 import java.io.StringWriter;
57 import java.net.URL;
58 import java.util.HashMap;
59 import java.util.Map;
60 import java.util.regex.Matcher;
61 import java.util.regex.Pattern;
62 import javax.inject.Singleton;
63
64
65
66
67
68
69
70
71 @Singleton
72 public class BetwixtModuleDefinitionReader implements ModuleDefinitionReader {
73 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BetwixtModuleDefinitionReader.class);
74
75 private static final String DTD = "/info/magnolia/module/model/module.dtd";
76
77 private final String dtdUrl;
78 private final BeanReader beanReader;
79
80 public BetwixtModuleDefinitionReader() {
81 final URL dtd = getClass().getResource(DTD);
82 if (dtd == null) {
83 throw new IllegalStateException("DTD not found at " + DTD);
84 }
85 dtdUrl = dtd.toString();
86
87 final BetwixtBindingStrategy bindingStrategy = new BetwixtBindingStrategy();
88 bindingStrategy.registerConverter(Version.class, new VersionConverter());
89
90 beanReader = new BeanReader();
91 try {
92 beanReader.getXMLIntrospector().getConfiguration().setTypeBindingStrategy(bindingStrategy);
93 beanReader.setValidating(true);
94 beanReader.setErrorHandler(new ErrorHandler());
95 beanReader.registerBeanClass(ModuleDefinition.class);
96 } catch (IntrospectionException e) {
97 throw new RuntimeException(e);
98 }
99 }
100
101 @Override
102 public Map<String, ModuleDefinition> readAll() throws ModuleManagementException {
103 final Map<String, ModuleDefinition> moduleDefinitions = new HashMap<String, ModuleDefinition>();
104
105 final String[] defResources = findModuleDescriptors();
106
107 for (final String resourcePath : defResources) {
108 log.debug("Parsing module file {}", resourcePath);
109 final ModuleDefinition def = readFromResource(resourcePath);
110 moduleDefinitions.put(def.getName(), def);
111 }
112 return moduleDefinitions;
113 }
114
115 protected String[] findModuleDescriptors() {
116 return ClasspathResourcesUtil.findResources(new ClasspathResourcesUtil.Filter() {
117 @Override
118 public boolean accept(String name) {
119 return name.startsWith("/META-INF/magnolia/") && name.endsWith(".xml");
120 }
121 });
122 }
123
124 @Override
125 public ModuleDefinition read(Reader in) throws ModuleManagementException {
126 try {
127 final Reader replacedDtd = replaceDtd(in);
128 return (ModuleDefinition) beanReader.parse(replacedDtd);
129 } catch (IOException e) {
130 throw new ModuleManagementException("Can't read module definition file: " + e.getMessage(), e);
131 } catch (SAXException e) {
132 throw new ModuleManagementException(e.getMessage(), e);
133 }
134 }
135
136 @Override
137 public ModuleDefinition readFromResource(String resourcePath) throws ModuleManagementException {
138 final InputStream resourceAsStream = getClass().getResourceAsStream(resourcePath);
139 if (resourceAsStream == null) {
140 throw new ModuleManagementException("Can't find module definition file at " + resourcePath);
141 }
142 final InputStreamReader reader = new InputStreamReader(resourceAsStream);
143 try {
144 return read(reader);
145 } finally {
146 try {
147 reader.close();
148 } catch (IOException e) {
149 log.error("Can't close input for " + resourcePath);
150 }
151 }
152 }
153
154
155
156
157
158
159
160
161 private Reader replaceDtd(Reader reader) throws IOException {
162 String content = IOUtils.toString(reader);
163
164
165 Pattern pattern = Pattern.compile("<!DOCTYPE .*>");
166 Matcher matcher = pattern.matcher(content);
167 content = matcher.replaceFirst("");
168
169
170 try {
171 Document doc = new SAXBuilder().build(new StringReader(content));
172 doc.setDocType(new DocType("module", dtdUrl));
173
174 XMLOutputter outputter = new XMLOutputter();
175 StringWriter writer = new StringWriter();
176 outputter.output(doc, writer);
177 final String replacedDtd = writer.toString();
178 return new StringReader(replacedDtd);
179 } catch (JDOMException e) {
180 throw new RuntimeException(e);
181 }
182 }
183
184 private static class ErrorHandler implements org.xml.sax.ErrorHandler {
185
186 @Override
187 public void warning(SAXParseException e) throws SAXException {
188 log.warn("Warning on module definition " + getSaxParseExceptionMessage(e));
189 }
190
191 @Override
192 public void error(SAXParseException e) throws SAXException {
193 throw new SAXException("Invalid module definition file, error " + getSaxParseExceptionMessage(e), e);
194 }
195
196 @Override
197 public void fatalError(SAXParseException e) throws SAXException {
198 throw new SAXException("Invalid module definition file, fatal error " + getSaxParseExceptionMessage(e), e);
199 }
200 }
201
202 private static String getSaxParseExceptionMessage(SAXParseException e) {
203 return "at line " + e.getLineNumber() + " column " + e.getColumnNumber() + ": " + e.getMessage();
204 }
205 }