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