View Javadoc

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   * Schema based on a XML document resource in the plugin
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         // default set of permissions for modules is pretty much unrestricted access to backend and front-end code
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 }