View Javadoc
1   package com.atlassian.plugin.schema.impl;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.ModuleDescriptorFactory;
5   import com.atlassian.plugin.Permissions;
6   import com.atlassian.plugin.Plugin;
7   import com.atlassian.plugin.PluginParseException;
8   import com.atlassian.plugin.RequirePermission;
9   import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
10  import com.atlassian.plugin.descriptors.CannotDisable;
11  import com.atlassian.plugin.module.ContainerManagedPlugin;
12  import com.atlassian.plugin.module.ModuleFactory;
13  import com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory;
14  import com.atlassian.plugin.osgi.factory.OsgiPlugin;
15  import com.atlassian.plugin.schema.descriptor.DescribedModuleDescriptorFactory;
16  import com.atlassian.plugin.schema.spi.DocumentBasedSchema;
17  import com.atlassian.plugin.schema.spi.Schema;
18  import com.atlassian.plugin.schema.spi.SchemaFactory;
19  import com.atlassian.plugin.schema.spi.SchemaTransformer;
20  import com.atlassian.plugin.util.resource.AlternativeResourceLoader;
21  import com.google.common.collect.ImmutableSet;
22  import org.dom4j.Element;
23  import org.osgi.framework.BundleContext;
24  
25  import javax.annotation.Nonnull;
26  import java.io.InputStream;
27  import java.net.URL;
28  import java.util.Collections;
29  import java.util.List;
30  
31  import static com.google.common.base.Preconditions.checkNotNull;
32  import static com.google.common.base.Preconditions.checkState;
33  import static com.google.common.collect.Iterables.transform;
34  import static java.util.Optional.ofNullable;
35  
36  /**
37   * Descriptor that allows described module descriptor factories to be configured in XML. Main value
38   * is the ability to reuse the name and description of the module descriptor configuration.
39   */
40  @CannotDisable
41  public final class DescribedModuleTypeModuleDescriptor extends AbstractModuleDescriptor<DescribedModuleDescriptorFactory> {
42      private static final String[] PUBLIC_INTERFACES = new String[]{
43              ModuleDescriptorFactory.class.getName(),
44              ListableModuleDescriptorFactory.class.getName(),
45              DescribedModuleDescriptorFactory.class.getName()
46      };
47  
48      private String schemaFactoryClassName;
49      private String type;
50      private String schemaTransformerClassName;
51      private String maxOccurs;
52      private Iterable<String> requiredPermissions;
53      private Iterable<String> optionalPermissions;
54  
55      public DescribedModuleTypeModuleDescriptor(ModuleFactory moduleFactory) {
56          super(moduleFactory);
57      }
58  
59      @Override
60      public void init(@Nonnull Plugin plugin, @Nonnull Element element) throws PluginParseException {
61          checkState(plugin instanceof OsgiPlugin, "Described module types can only be declared in OSGi Plugins, %s is not such a plugin", plugin.getKey());
62  
63          super.init(plugin, element);
64  
65          this.type = getOptionalAttribute(element, "type", getKey());
66          this.schemaFactoryClassName = getOptionalAttribute(element, "schema-factory-class", null);
67          this.schemaTransformerClassName = getOptionalAttribute(element, "schema-transformer-class", null);
68          this.maxOccurs = getOptionalAttribute(element, "max-occurs", "unbounded");
69          this.requiredPermissions = getPermissions(element.element("required-permissions"));
70          this.optionalPermissions = getPermissions(element.element("optional-permissions"));
71      }
72  
73      private static Iterable<String> getPermissions(Element element) {
74          return ofNullable(element)
75                  .map(e -> transform(getElements(e, "permission"), Element::getTextTrim))
76                  .orElseGet(Collections::emptyList);
77      }
78  
79      @SuppressWarnings("unchecked")
80      private static List<Element> getElements(Element element, String name) {
81          return element.elements(name);
82      }
83  
84      @Override
85      public void enabled() {
86          checkState(plugin instanceof OsgiPlugin, "Described module types can only be declared in OSGi Plugins, %s is not such a plugin", plugin.getKey());
87  
88          super.enabled();
89  
90          final SchemaTransformer schemaTransformer = schemaTransformerClassName != null
91                  ? create(findClass(schemaTransformerClassName, SchemaTransformer.class))
92                  : SchemaTransformer.IDENTITY;
93  
94          final Class<? extends ModuleDescriptor> moduleClass = findClass(moduleClassName, ModuleDescriptor.class);
95  
96          final SchemaFactory schemaFactory = schemaFactoryClassName != null
97                  ? create(findClass(schemaFactoryClassName, SchemaFactory.class))
98                  : buildSingleton(DocumentBasedSchema.builder(type)
99                          .setResourceLoader(new AlternativePluginResourceLoader(plugin))
100                         .setName(getDisplayName())
101                         .setDescription(getDescription() != null ? getDescription() : "")
102                         .setTransformer(schemaTransformer)
103                         .setMaxOccurs(maxOccurs)
104                         .setRequiredPermissions(getModuleRequiredPermissions(moduleClass))
105                         .setOptionalPermissions(optionalPermissions)
106                         .build()
107         );
108 
109         @SuppressWarnings("unchecked")
110         final DescribedModuleDescriptorFactory factory = new DescribedModuleTypeDescribedModuleDescriptorFactory(
111                 (ContainerManagedPlugin) plugin,
112                 type,
113                 moduleClass,
114                 schemaFactory);
115 
116         getBundleContext().registerService(PUBLIC_INTERFACES, factory, null);
117     }
118 
119     /**
120      * Gets the required module description for the new module descriptor being defined.
121      * This adds together the permissions defined in the plugin descriptor, see {@link #requiredPermissions} and the ones
122      * defined on the module class itself, see {@link RequirePermission}.
123      */
124     private Iterable<String> getModuleRequiredPermissions(Class<? extends ModuleDescriptor> moduleClass) {
125         return ImmutableSet.<String>builder()
126                 .addAll(requiredPermissions)
127                 .addAll(Permissions.getRequiredPermissions(moduleClass))
128                 .build();
129     }
130 
131     private BundleContext getBundleContext() {
132         return ((OsgiPlugin) plugin).getBundle().getBundleContext();
133     }
134 
135     private <T> T create(Class<? extends T> type) {
136         return ((ContainerManagedPlugin) plugin).getContainerAccessor().createBean(type);
137     }
138 
139     private <T> Class<? extends T> findClass(String className, Class<T> castTo) {
140         checkNotNull(className);
141         Class<T> clazz;
142         try {
143             clazz = plugin.loadClass(className, getClass());
144         } catch (ClassNotFoundException e) {
145             throw new PluginParseException("Unable to find class " + className);
146         }
147         return clazz.asSubclass(castTo);
148     }
149 
150     @Override
151     public DescribedModuleDescriptorFactory getModule() {
152         return moduleFactory.createModule(moduleClassName, this);
153     }
154 
155     private SchemaFactory buildSingleton(final Schema schema) {
156         return () -> schema;
157     }
158 
159     public static String getOptionalAttribute(Element e, String name, Object defaultValue) {
160         String value = e.attributeValue(name);
161         return value != null ? value :
162                 defaultValue != null ? defaultValue.toString() : null;
163     }
164 
165     private static final class AlternativePluginResourceLoader implements AlternativeResourceLoader {
166         private final Plugin plugin;
167 
168         public AlternativePluginResourceLoader(final Plugin plugin) {
169             this.plugin = checkNotNull(plugin);
170         }
171 
172         @Override
173         public URL getResource(final String path) {
174             return plugin.getResource(path);
175         }
176 
177         @Override
178         public InputStream getResourceAsStream(final String name) {
179             return plugin.getResourceAsStream(name);
180         }
181     }
182 }