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