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