View Javadoc

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