View Javadoc
1   package com.atlassian.plugin.osgi.factory.transform;
2   
3   import com.atlassian.plugin.Application;
4   import com.atlassian.plugin.InstallationMode;
5   import com.atlassian.plugin.PluginArtifact;
6   import com.atlassian.plugin.PluginParseException;
7   import com.atlassian.plugin.osgi.container.OsgiContainerManager;
8   import com.atlassian.plugin.osgi.factory.OsgiPlugin;
9   import com.atlassian.plugin.osgi.factory.transform.model.ComponentImport;
10  import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
11  import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
12  import com.atlassian.plugin.parsers.XmlDescriptorParser;
13  import com.google.common.collect.ImmutableMap;
14  import com.google.common.collect.ImmutableSet;
15  import org.apache.commons.io.IOUtils;
16  import org.dom4j.Document;
17  import org.dom4j.Element;
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  
21  import java.io.File;
22  import java.io.InputStream;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.LinkedHashMap;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.jar.JarEntry;
31  import java.util.jar.Manifest;
32  
33  import static com.atlassian.plugin.osgi.factory.transform.JarUtils.getEntries;
34  import static com.atlassian.plugin.osgi.factory.transform.JarUtils.getEntry;
35  import static com.atlassian.plugin.osgi.factory.transform.JarUtils.hasManifestEntry;
36  import static com.google.common.base.Preconditions.checkNotNull;
37  
38  /**
39   * The transform context containing any configuration necessary to enact a JAR transformation.
40   *
41   * @since 2.2.0
42   */
43  public final class TransformContext {
44      private final Manifest manifest;
45      private final List<HostComponentRegistration> regs;
46      private final Map<String, byte[]> fileOverrides;
47      private final Map<String, String> bndInstructions;
48      private final Document descriptorDocument;
49      private final List<String> extraImports;
50      private final List<String> extraExports;
51      private final Set<String> bundleClassPathJars;
52      private final PluginArtifact pluginArtifact;
53      private final Map<String, ComponentImport> componentImports;
54      private final SystemExports systemExports;
55      private final Set<Application> applications;
56      private boolean shouldRequireSpring = false;
57      private final OsgiContainerManager osgiContainerManager;
58      private final Set<HostComponentRegistration> requiredHostComponents;
59  
60      private static final Logger LOG = LoggerFactory.getLogger(TransformContext.class);
61  
62      // The transformation is mainly about generating spring beans.
63      // We don't want to have name conflicts between them. This map helps keep track of that.
64      // The definition of this map is "beanName -> source".
65      private final Map<String, String> beanSourceMap = new LinkedHashMap<>();
66  
67      public TransformContext(final List<HostComponentRegistration> regs, final SystemExports systemExports, final PluginArtifact pluginArtifact, final Set<Application> applications, final String descriptorPath, final OsgiContainerManager osgiContainerManager) {
68          this.regs = regs;
69          this.systemExports = checkNotNull(systemExports);
70          this.osgiContainerManager = checkNotNull(osgiContainerManager);
71          this.pluginArtifact = checkNotNull(pluginArtifact);
72          this.applications = applications == null ? ImmutableSet.of() : applications;
73  
74          manifest = JarUtils.getManifest(pluginArtifact.toFile());
75          fileOverrides = new LinkedHashMap<>();
76          bndInstructions = new LinkedHashMap<>();
77          descriptorDocument = retrieveDocFromJar(pluginArtifact, checkNotNull(descriptorPath));
78          extraImports = new ArrayList<>();
79          extraExports = new ArrayList<>();
80          bundleClassPathJars = new LinkedHashSet<>();
81  
82          componentImports = parseComponentImports(descriptorDocument);
83          requiredHostComponents = new LinkedHashSet<>();
84      }
85  
86      public File getPluginFile() {
87          return pluginArtifact.toFile();
88      }
89  
90      public PluginArtifact getPluginArtifact() {
91          return pluginArtifact;
92      }
93  
94      public List<HostComponentRegistration> getHostComponentRegistrations() {
95          return regs;
96      }
97  
98      public Map<String, byte[]> getFileOverrides() {
99          return fileOverrides;
100     }
101 
102     public Map<String, String> getBndInstructions() {
103         return bndInstructions;
104     }
105 
106     public Document getDescriptorDocument() {
107         return descriptorDocument;
108     }
109 
110     public Manifest getManifest() {
111         return manifest;
112     }
113 
114     public List<String> getExtraImports() {
115         return extraImports;
116     }
117 
118     public List<String> getExtraExports() {
119         return extraExports;
120     }
121 
122     public void addBundleClasspathJar(String classpath) {
123         bundleClassPathJars.add(classpath);
124     }
125 
126     public Set<String> getBundleClassPathJars() {
127         return Collections.unmodifiableSet(bundleClassPathJars);
128     }
129 
130     public Map<String, ComponentImport> getComponentImports() {
131         return componentImports;
132     }
133 
134     public SystemExports getSystemExports() {
135         return systemExports;
136     }
137 
138     public Set<Application> getApplications() {
139         return ImmutableSet.copyOf(applications);
140     }
141 
142     public boolean shouldRequireSpring() {
143         return shouldRequireSpring;
144     }
145 
146     public void setShouldRequireSpring(final boolean shouldRequireSpring) {
147         this.shouldRequireSpring = shouldRequireSpring;
148         if (shouldRequireSpring) {
149             getFileOverrides().put("META-INF/spring/", new byte[0]);
150         }
151     }
152 
153     public OsgiContainerManager getOsgiContainerManager() {
154         return osgiContainerManager;
155     }
156 
157     public Iterable<JarEntry> getPluginJarEntries() {
158         return getEntries(pluginArtifact.toFile());
159     }
160 
161     public JarEntry getPluginJarEntry(final String path) {
162         return getEntry(pluginArtifact.toFile(), path);
163     }
164 
165     public void addRequiredHostComponent(final HostComponentRegistration hostComponent) {
166         requiredHostComponents.add(hostComponent);
167     }
168 
169     public Set<HostComponentRegistration> getRequiredHostComponents() {
170         return requiredHostComponents;
171     }
172 
173     /**
174      * Track a bean by remembering its name and source.
175      * If there is already a bean with the same name, {@link PluginTransformationException} will be raised.
176      *
177      * @param name   id, name, or alias of the bean = basically any names which can be used to refer to the bean in Spring context.
178      * @param source the source of the bean.
179      */
180     public void trackBean(final String name, final String source) throws PluginTransformationException {
181         checkNotNull(name, "empty bean name");
182         checkNotNull(source, "source of bean is required");
183 
184         // if it already exists, just explode.
185         if (beanSourceMap.containsKey(name)) {
186             final String message = String.format("The bean identifier '%s' is used by two different beans from %s and %s."
187                             + "This is a bad practice and may not be supported in newer plugin framework version.",
188                     name, source, beanSourceMap.get(name));
189             // TODO: turn this warning into exception in the future. PLUG-682
190             LOG.warn(message);
191             //throw new PluginTransformationException(message);
192         }
193 
194         // otherwise, just track it.
195         beanSourceMap.put(name, source);
196     }
197 
198     /**
199      * Check if the bean has been tracked.
200      * This is used for testing only.
201      *
202      * @param name the bean name.
203      */
204     public boolean beanExists(final String name) {
205         return beanSourceMap.containsKey(name);
206     }
207 
208     //
209     // static utils
210     //
211 
212     private static Map<String, ComponentImport> parseComponentImports(final Document descriptorDocument) {
213         final Map<String, ComponentImport> componentImports = new LinkedHashMap<>();
214         @SuppressWarnings("unchecked")
215         final List<Element> elements = descriptorDocument.getRootElement().elements("component-import");
216         for (final Element component : elements) {
217             final ComponentImport ci = new ComponentImport(component);
218             componentImports.put(ci.getKey(), ci);
219         }
220         return ImmutableMap.copyOf(componentImports);
221     }
222 
223     private static Document retrieveDocFromJar(final PluginArtifact pluginArtifact, final String descriptorPath) throws PluginTransformationException {
224         InputStream stream = null;
225         try {
226             stream = pluginArtifact.getResourceAsStream(descriptorPath);
227             if (stream == null) {
228                 throw new PluginTransformationException("Unable to access descriptor " + descriptorPath);
229             }
230             return new DocumentExposingDescriptorParser(stream).getDocument();
231         } finally {
232             IOUtils.closeQuietly(stream);
233         }
234     }
235 
236     public InstallationMode getInstallationMode() {
237         return hasManifestEntry(manifest, OsgiPlugin.REMOTE_PLUGIN_KEY) ? InstallationMode.REMOTE : InstallationMode.LOCAL;
238     }
239 
240     /**
241      * Get the document (protected method on super-class).
242      */
243     private static class DocumentExposingDescriptorParser extends XmlDescriptorParser {
244         /**
245          * @throws com.atlassian.plugin.PluginParseException if there is a problem reading the descriptor from the XML {@link java.io.InputStream}.
246          */
247         DocumentExposingDescriptorParser(final InputStream source) throws PluginParseException {
248             // A null application key is fine here as we are only interested in the parsed document
249             super(source, ImmutableSet.of());
250         }
251 
252         @Override
253         public Document getDocument() {
254             return super.getDocument();
255         }
256     }
257 }