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
38
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
121
122
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 }