1 package com.atlassian.plugin.osgi.factory.transform.stage;
2
3 import com.atlassian.plugin.osgi.factory.transform.PluginTransformationException;
4 import com.atlassian.plugin.osgi.factory.transform.TransformContext;
5 import com.atlassian.plugin.osgi.factory.transform.TransformStage;
6 import com.atlassian.plugin.util.PluginUtils;
7 import com.atlassian.plugin.util.validation.ValidationPattern;
8 import com.google.common.collect.Sets;
9 import org.apache.commons.lang3.StringUtils;
10 import org.dom4j.Document;
11 import org.dom4j.Element;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.LinkedHashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.jar.JarInputStream;
24
25 import static com.atlassian.plugin.util.validation.ValidationPattern.createPattern;
26 import static com.atlassian.plugin.util.validation.ValidationPattern.test;
27
28
29
30
31
32
33 public class ComponentSpringStage implements TransformStage {
34
35
36
37 private static final String SPRING_XML = "META-INF/spring/atlassian-plugins-components.xml";
38
39 public static final String BEAN_SOURCE = "Plugin Component";
40
41 private static final Logger LOG = LoggerFactory.getLogger(ComponentSpringStage.class);
42
43 public void execute(TransformContext context) throws PluginTransformationException {
44 if (SpringHelper.shouldGenerateFile(context, SPRING_XML)) {
45 Document springDoc = SpringHelper.createSpringDocument();
46 Element root = springDoc.getRootElement();
47 List<Element> elements = context.getDescriptorDocument().getRootElement().elements("component");
48
49 ValidationPattern validation = createPattern().
50 rule(
51 test("@key").withError("The key is required"),
52 test("@class").withError("The class is required"),
53 test("not(@public='true') or interface or @interface").withError("Interfaces must be declared for public components"),
54 test("not(service-properties) or count(service-properties/entry[@key and @value]) > 0")
55 .withError("The service-properties element must contain at least one entry element with key and value attributes"));
56
57 final Set<String> declaredInterfaces = new LinkedHashSet<>();
58
59 for (Element component : elements) {
60 if (!PluginUtils.doesModuleElementApplyToApplication(component, context.getApplications(), context.getInstallationMode())) {
61 continue;
62 }
63 validation.evaluate(component);
64
65
66 String beanId = component.attributeValue("key");
67
68 context.trackBean(beanId, BEAN_SOURCE);
69
70 Element bean = root.addElement("beans:bean");
71 bean.addAttribute("id", beanId);
72 bean.addAttribute("autowire", "default");
73
74
75 if (!StringUtils.isBlank(component.attributeValue("alias"))) {
76 Element alias = root.addElement("beans:alias");
77 alias.addAttribute("name", beanId);
78 alias.addAttribute("alias", component.attributeValue("alias"));
79 }
80
81 List<String> interfaceNames = new ArrayList<>();
82 List<Element> compInterfaces = component.elements("interface");
83 for (Element inf : compInterfaces) {
84 interfaceNames.add(inf.getTextTrim());
85 }
86 if (component.attributeValue("interface") != null) {
87 interfaceNames.add(component.attributeValue("interface"));
88 }
89
90 bean.addAttribute("class", component.attributeValue("class"));
91
92 if ("true".equalsIgnoreCase(component.attributeValue("public"))) {
93 Element osgiService = root.addElement("osgi:service");
94 osgiService.addAttribute("id", component.attributeValue("key") + "_osgiService");
95 osgiService.addAttribute("ref", component.attributeValue("key"));
96
97
98
99 declaredInterfaces.addAll(interfaceNames);
100
101 Element interfaces = osgiService.addElement("osgi:interfaces");
102 for (String name : interfaceNames) {
103 ensureExported(name, context);
104 Element e = interfaces.addElement("beans:value");
105 e.setText(name);
106 }
107
108 Element svcprops = component.element("service-properties");
109 if (svcprops != null) {
110 Element targetSvcprops = osgiService.addElement("osgi:service-properties");
111 for (Element prop : new ArrayList<Element>(svcprops.elements("entry"))) {
112 Element e = targetSvcprops.addElement("beans:entry");
113 e.addAttribute("key", prop.attributeValue("key"));
114 e.addAttribute("value", prop.attributeValue("value"));
115 }
116 }
117 }
118 }
119
120 if (root.elements().size() > 0) {
121 context.setShouldRequireSpring(true);
122 context.getFileOverrides().put(SPRING_XML, SpringHelper.documentToBytes(springDoc));
123 }
124
125
126 Set<String> requiredInterfaces;
127 try {
128 requiredInterfaces = calculateRequiredImports(context.getPluginFile(),
129 declaredInterfaces,
130 context.getBundleClassPathJars());
131 } catch (PluginTransformationException e) {
132 throw new PluginTransformationException("Error while calculating import manifest", e);
133 }
134
135
136 context.getExtraImports().addAll(TransformStageUtils.getPackageNames(requiredInterfaces));
137 }
138 }
139
140 private void ensureExported(String className, TransformContext context) {
141 String pkg = className.substring(0, className.lastIndexOf('.'));
142 if (!context.getExtraExports().contains(pkg)) {
143 String fileName = className.replace('.', '/') + ".class";
144
145 if (context.getPluginArtifact().doesResourceExist(fileName)) {
146 context.getExtraExports().add(pkg);
147 }
148 }
149 }
150
151
152
153
154
155
156 private Set<String> calculateRequiredImports(final File pluginFile,
157 final Set<String> declaredInterfaces,
158 final Set<String> innerJars) {
159
160 if (declaredInterfaces.size() > 0) {
161
162 final Set<String> shallowMatches;
163 FileInputStream fis = null;
164 JarInputStream jarStream = null;
165 try {
166 fis = new FileInputStream(pluginFile);
167 jarStream = new JarInputStream(fis);
168 shallowMatches = TransformStageUtils.scanJarForItems(jarStream,
169 declaredInterfaces,
170 TransformStageUtils.JarEntryToClassName.INSTANCE);
171 } catch (final IOException ioe) {
172 throw new PluginTransformationException("Error reading jar:" + pluginFile.getName(), ioe);
173 } finally {
174 TransformStageUtils.closeNestedStreamQuietly(jarStream, fis);
175 }
176
177
178 final Set<String> remainders = Sets.newLinkedHashSet(Sets.difference(declaredInterfaces, shallowMatches));
179
180
181
182 if ((remainders.size() > 0) && (innerJars.size() > 0)) {
183 remainders.removeAll(TransformStageUtils.scanInnerJars(pluginFile, innerJars, remainders));
184 }
185
186 return Collections.unmodifiableSet(remainders);
187 }
188
189
190 return Collections.emptySet();
191 }
192 }