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 @Singleton
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 @Override
99 public Map<String, ModuleDefinition> readAll() throws ModuleManagementException {
100 final Map<String, ModuleDefinition> moduleDefinitions = new HashMap<String, ModuleDefinition>();
101
102 final String[] defResources = findModuleDescriptors();
103
104 for (final String resourcePath : defResources) {
105 log.debug("Parsing module file {}", resourcePath);
106 final ModuleDefinition def = readFromResource(resourcePath);
107 moduleDefinitions.put(def.getName(), def);
108 }
109 return moduleDefinitions;
110 }
111
112 protected String[] findModuleDescriptors() {
113 return ClasspathResourcesUtil.findResources(new ClasspathResourcesUtil.Filter() {
114 @Override
115 public boolean accept(String name) {
116 return name.startsWith("/META-INF/magnolia/") && name.endsWith(".xml");
117 }
118 });
119 }
120
121 @Override
122 public ModuleDefinition read(Reader in) throws ModuleManagementException {
123 try {
124 final Reader replacedDtd = replaceDtd(in);
125 return (ModuleDefinition) beanReader.parse(replacedDtd);
126 } catch (IOException e) {
127 throw new ModuleManagementException("Can't read module definition file: " + e.getMessage(), e);
128 } catch (SAXException e) {
129 throw new ModuleManagementException(e.getMessage(), e);
130 }
131 }
132
133 @Override
134 public ModuleDefinition readFromResource(String resourcePath) throws ModuleManagementException {
135 final InputStream resourceAsStream = getClass().getResourceAsStream(resourcePath);
136 if (resourceAsStream == null) {
137 throw new ModuleManagementException("Can't find module definition file at " + resourcePath);
138 }
139 final InputStreamReader reader = new InputStreamReader(resourceAsStream);
140 try {
141 return read(reader);
142 } finally {
143 try {
144 reader.close();
145 } catch (IOException e) {
146 log.error("Can't close input for " + resourcePath);
147 }
148 }
149 }
150
151
152
153
154
155
156
157
158 private Reader replaceDtd(Reader reader) throws IOException {
159 String content = IOUtils.toString(reader);
160
161
162 Pattern pattern = Pattern.compile("<!DOCTYPE .*>");
163 Matcher matcher = pattern.matcher(content);
164 content = matcher.replaceFirst("");
165
166
167 try {
168 Document doc = new SAXBuilder().build(new StringReader(content));
169 doc.setDocType(new DocType("module", dtdUrl));
170
171 XMLOutputter outputter = new XMLOutputter();
172 StringWriter writer = new StringWriter();
173 outputter.output(doc, writer);
174 final String replacedDtd = writer.toString();
175 return new StringReader(replacedDtd);
176 } catch (JDOMException e) {
177 throw new RuntimeException(e);
178 }
179 }
180
181 private static class ErrorHandler implements org.xml.sax.ErrorHandler {
182
183 @Override
184 public void warning(SAXParseException e) throws SAXException {
185 log.warn("Warning on module definition " + getSaxParseExceptionMessage(e));
186 }
187
188 @Override
189 public void error(SAXParseException e) throws SAXException {
190 throw new SAXException("Invalid module definition file, error " + getSaxParseExceptionMessage(e), e);
191 }
192
193 @Override
194 public void fatalError(SAXParseException e) throws SAXException {
195 throw new SAXException("Invalid module definition file, fatal error " + getSaxParseExceptionMessage(e), e);
196 }
197 }
198
199 private static String getSaxParseExceptionMessage(SAXParseException e) {
200 return "at line " + e.getLineNumber() + " column " + e.getColumnNumber() + ": " + e.getMessage();
201 }
202 }