1 package com.atlassian.plugin.schema.spi;
2
3 import com.atlassian.plugin.Permissions;
4 import com.atlassian.plugin.Plugin;
5 import com.google.common.collect.ImmutableSet;
6 import org.dom4j.Document;
7 import org.dom4j.DocumentException;
8 import org.dom4j.io.SAXReader;
9 import org.xml.sax.EntityResolver;
10 import org.xml.sax.InputSource;
11 import org.xml.sax.SAXException;
12 import org.xml.sax.XMLReader;
13
14 import javax.xml.XMLConstants;
15 import javax.xml.parsers.ParserConfigurationException;
16 import javax.xml.parsers.SAXParserFactory;
17 import java.io.ByteArrayInputStream;
18 import java.io.IOException;
19 import java.net.URL;
20
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static java.util.Collections.emptySet;
23
24
25
26
27 public final class DocumentBasedSchema implements Schema
28 {
29 private final String name;
30 private final String description;
31 private final String path;
32 private final String elementName;
33 private final String fileName;
34 private final String complexType;
35 private final String maxOccurs;
36 private final Iterable<String> requiredPermissions;
37 private final Iterable<String> optionalPermissions;
38 private final Plugin plugin;
39 private final SchemaTransformer schemaTransformer;
40
41 private DocumentBasedSchema(String elementName,
42 String name,
43 String description,
44 String path,
45 String fileName,
46 String complexType,
47 String maxOccurs,
48 Iterable<String> requiredPermissions,
49 Iterable<String> optionalPermissions,
50 Plugin plugin,
51 SchemaTransformer schemaTransformer
52 )
53 {
54 this.name = name;
55 this.elementName = elementName;
56 this.description = description;
57 this.path = path;
58 this.fileName = fileName;
59 this.complexType = complexType;
60 this.maxOccurs = maxOccurs;
61 this.requiredPermissions = requiredPermissions;
62 this.optionalPermissions = optionalPermissions;
63 this.plugin = plugin;
64 this.schemaTransformer = schemaTransformer;
65 }
66
67 @Override
68 public String getName()
69 {
70 return name;
71 }
72
73 @Override
74 public String getDescription()
75 {
76 return description;
77 }
78
79 @Override
80 public String getFileName()
81 {
82 return fileName;
83 }
84
85 @Override
86 public String getElementName()
87 {
88 return elementName;
89 }
90
91 @Override
92 public String getComplexType()
93 {
94 return complexType;
95 }
96
97 @Override
98 public String getMaxOccurs()
99 {
100 return maxOccurs;
101 }
102
103 @Override
104 public Iterable<String> getRequiredPermissions()
105 {
106 return requiredPermissions;
107 }
108
109 @Override
110 public Iterable<String> getOptionalPermissions()
111 {
112 return optionalPermissions;
113 }
114
115 @Override
116 public Document getDocument()
117 {
118 final URL sourceUrl = plugin.getResource(path);
119 if (sourceUrl == null)
120 {
121 throw new IllegalStateException("Cannot find schema document " + path);
122 }
123 Document source = parseDocument(sourceUrl);
124 return schemaTransformer.transform(source);
125 }
126
127 public static DynamicSchemaBuilder builder()
128 {
129 return new DynamicSchemaBuilder();
130 }
131
132 public static DynamicSchemaBuilder builder(String id)
133 {
134 return new DynamicSchemaBuilder(id);
135 }
136
137 public static class DynamicSchemaBuilder
138 {
139 private String name;
140 private String description;
141 private String path;
142 private String fileName;
143 private String elementName;
144 private String complexType;
145 private String maxOccurs = "unbounded";
146
147
148 private Iterable<String> requiredPermissions = ImmutableSet.of(Permissions.EXECUTE_JAVA, Permissions.GENERATE_ANY_HTML);
149
150 private Iterable<String> optionalPermissions = emptySet();
151 private Plugin plugin;
152 private SchemaTransformer schemaTransformer = SchemaTransformer.IDENTITY;
153
154 public DynamicSchemaBuilder()
155 {
156 }
157
158 public DynamicSchemaBuilder(String elementName)
159 {
160 this.elementName = elementName;
161 this.fileName = elementName + ".xsd";
162 this.path = "/xsd/" + this.fileName;
163 this.complexType = IdUtils.dashesToCamelCase(elementName) + "Type";
164 this.name = IdUtils.dashesToTitle(elementName);
165 this.description = "A " + name + " module";
166 }
167
168 public DynamicSchemaBuilder setName(String name)
169 {
170 this.name = name;
171 return this;
172 }
173
174 public DynamicSchemaBuilder setDescription(String description)
175 {
176 this.description = description;
177 return this;
178 }
179
180 public DynamicSchemaBuilder setPath(String path)
181 {
182 this.path = path;
183 return this;
184 }
185
186 public DynamicSchemaBuilder setFileName(String fileName)
187 {
188 this.fileName = fileName;
189 return this;
190 }
191
192 public DynamicSchemaBuilder setElementName(String elementName)
193 {
194 this.elementName = elementName;
195 return this;
196 }
197
198 public DynamicSchemaBuilder setRequiredPermissions(Iterable<String> permissions)
199 {
200 this.requiredPermissions = permissions;
201 return this;
202 }
203
204 public DynamicSchemaBuilder setOptionalPermissions(Iterable<String> permissions)
205 {
206 this.optionalPermissions = permissions;
207 return this;
208 }
209
210 public DynamicSchemaBuilder setComplexType(String complexType)
211 {
212 this.complexType = complexType;
213 return this;
214 }
215
216 public DynamicSchemaBuilder setMaxOccurs(String maxOccurs)
217 {
218 this.maxOccurs = maxOccurs;
219 return this;
220 }
221
222 public DynamicSchemaBuilder setPlugin(Plugin plugin)
223 {
224 this.plugin = plugin;
225 return this;
226 }
227
228 public DynamicSchemaBuilder setTransformer(SchemaTransformer schemaTransformer)
229 {
230 this.schemaTransformer = schemaTransformer;
231 return this;
232 }
233
234 public DocumentBasedSchema build()
235 {
236 checkNotNull(elementName);
237 checkNotNull(fileName);
238 checkNotNull(name);
239 checkNotNull(description);
240 checkNotNull(complexType);
241 checkNotNull(plugin);
242 checkNotNull(requiredPermissions);
243 checkNotNull(optionalPermissions);
244 return new DocumentBasedSchema(elementName, name, description, path, fileName, complexType, maxOccurs,
245 requiredPermissions, optionalPermissions, plugin,
246 schemaTransformer);
247 }
248 }
249
250 private static Document parseDocument(URL xmlUrl)
251 {
252 Document source;
253 try
254 {
255 source = createSecureSaxReader().read(xmlUrl);
256 }
257 catch (DocumentException e)
258 {
259 throw new IllegalArgumentException("Unable to parse XML", e);
260 }
261
262 return source;
263 }
264
265 public static SAXReader createSecureSaxReader()
266 {
267 return createReader(false);
268 }
269
270 private static SAXReader createReader(boolean validating)
271 {
272 XMLReader xmlReader;
273 try
274 {
275 SAXParserFactory spf = SAXParserFactory.newInstance();
276 spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",
277 false);
278 spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
279 xmlReader = spf.newSAXParser().getXMLReader();
280 xmlReader.setEntityResolver(EMPTY_ENTITY_RESOLVER);
281 }
282 catch (ParserConfigurationException e)
283 {
284 throw new RuntimeException("XML Parser configured incorrectly", e);
285 }
286 catch (SAXException e)
287 {
288 throw new RuntimeException("Unable to configure XML parser", e);
289 }
290 return new SAXReader(xmlReader, validating);
291 }
292
293 private static InputSource EMPTY_INPUT_SOURCE = new InputSource(new ByteArrayInputStream(new byte[0]));
294
295 private static final EntityResolver EMPTY_ENTITY_RESOLVER = new EntityResolver()
296 {
297 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException
298 {
299 return EMPTY_INPUT_SOURCE;
300 }
301 };
302 }