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