View Javadoc

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 com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
7   import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
8   import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
9   import com.atlassian.plugin.osgi.util.Clazz;
10  import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
11  import com.atlassian.plugin.PluginParseException;
12  import org.dom4j.Document;
13  import org.dom4j.Element;
14  import org.apache.commons.io.IOUtils;
15  import org.apache.commons.logging.Log;
16  import org.apache.commons.logging.LogFactory;
17  import org.osgi.framework.Constants;
18  
19  import java.util.*;
20  import java.util.jar.Manifest;
21  import java.util.zip.ZipInputStream;
22  import java.util.zip.ZipEntry;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  
27  public class HostComponentSpringStage implements TransformStage
28  {
29      private static final Log log = LogFactory.getLog(HostComponentSpringStage.class);
30  
31      /** Path of generated Spring XML file */
32      static final String SPRING_XML = "META-INF/spring/atlassian-plugins-host-components.xml";
33  
34      public void execute(TransformContext context) throws PluginTransformationException
35      {
36          if (SpringHelper.shouldGenerateFile(context, SPRING_XML))
37          {
38              Document doc = SpringHelper.createSpringDocument();
39              Set<String> hostComponentInterfaceNames = convertRegistrationsToSet(context.getHostComponentRegistrations());
40              Set<String> matchedInterfaceNames = new HashSet<String>();
41              List<String> innerJarPaths = findJarPaths(context.getManifest());
42              try
43              {
44                  findUsedHostComponents(hostComponentInterfaceNames, matchedInterfaceNames, innerJarPaths, new FileInputStream(context.getPluginFile()));
45              }
46              catch (IOException e)
47              {
48                  throw new PluginParseException("Unable to scan for host components in plugin classes", e);
49              }
50  
51  
52              List<HostComponentRegistration> matchedRegistrations = new ArrayList<HostComponentRegistration>();
53              Element root = doc.getRootElement();
54              if (context.getHostComponentRegistrations() != null)
55              {
56                  int index = -1;
57                  for (HostComponentRegistration reg : context.getHostComponentRegistrations())
58                  {
59                      index++;
60                      boolean found = false;
61                      for (String name : reg.getMainInterfaces())
62                      {
63                          if (matchedInterfaceNames.contains(name))
64                          {
65                              found = true;
66                          }
67                      }
68                      if (!found)
69                      {
70                          continue;
71                      }
72                      matchedRegistrations.add(reg);
73  
74                      String beanName = reg.getProperties().get(PropertyBuilder.BEAN_NAME);
75  
76                      Element osgiService = root.addElement("osgi:reference");
77                      osgiService.addAttribute("id", determineId(context.getDescriptorDocument(), beanName, index));
78  
79                      if (beanName != null)
80                      {
81                          osgiService.addAttribute("filter", "(&(bean-name=" + beanName + ")(" + ComponentRegistrar.HOST_COMPONENT_FLAG + "=true))");
82                      }
83  
84                      Element interfaces = osgiService.addElement("osgi:interfaces");
85                      for (String name : reg.getMainInterfaces())
86                      {
87                          Element e = interfaces.addElement("beans:value");
88                          e.setText(name);
89                      }
90                  }
91              }
92              addImportsForMatchedHostComponents(matchedRegistrations, context.getExtraImports());
93              if (root.elements().size() > 0)
94              {
95                  context.getFileOverrides().put(SPRING_XML, SpringHelper.documentToBytes(doc));
96              }
97          }
98      }
99  
100     private void addImportsForMatchedHostComponents(List<HostComponentRegistration> matchedRegistrations, List<String> extraImports)
101     {
102         try
103         {
104             String list = OsgiHeaderUtil.findReferredPackages(matchedRegistrations);
105             extraImports.addAll(Arrays.asList(list.split(",")));
106         }
107         catch (IOException e)
108         {
109             throw new PluginTransformationException("Unable to scan for host component referred packages", e);
110         }
111     }
112 
113 
114     private Set<String> convertRegistrationsToSet(List<HostComponentRegistration> regs)
115     {
116         Set<String> interfaceNames = new HashSet<String>();
117         if (regs != null)
118         {
119             for (HostComponentRegistration reg : regs)
120             {
121                 interfaceNames.addAll(Arrays.asList(reg.getMainInterfaces()));
122             }
123         }
124         return interfaceNames;
125     }
126 
127     private void findUsedHostComponents(Set<String> allHostComponents, Set<String> matchedHostComponents, List<String> innerJarPaths, InputStream
128             jarStream) throws IOException
129     {
130 
131         ZipInputStream zin = null;
132         try
133         {
134             zin = new ZipInputStream(jarStream);
135             ZipEntry zipEntry;
136             while ((zipEntry = zin.getNextEntry()) != null)
137             {
138                 String path = zipEntry.getName();
139                 if (path.endsWith(".class"))
140                 {
141                     Clazz cls = new Clazz(path, new UnclosableInputStream(zin));
142                     Set<String> referredClasses = cls.getReferredClasses();
143                     for (String ref : referredClasses)
144                     {
145                         String name = ref.replaceAll("/", ".").substring(0, ref.length() - ".class".length());
146                         if (allHostComponents.contains(name))
147                         {
148                             matchedHostComponents.add(name);
149                         }
150 
151                     }
152                 }
153                 else if (path.endsWith(".jar") && innerJarPaths.contains(path))
154                 {
155                     findUsedHostComponents(allHostComponents, matchedHostComponents, null, new UnclosableInputStream(zin));
156                 }
157             }
158         }
159         finally
160         {
161             IOUtils.closeQuietly(zin);
162         }
163     }
164 
165     private List<String> findJarPaths(Manifest mf)
166     {
167         List<String> paths = new ArrayList<String>();
168         String cp = mf.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH);
169         if (cp != null)
170         {
171             for (String entry : cp.split(","))
172             {
173                 entry = entry.trim();
174                 if (entry.length() != 1 && entry.endsWith(".jar"))
175                 {
176                     paths.add(entry);
177                 }
178                 else
179                 {
180                     log.warn("Non-jar classpath elements not supported: " + entry);
181                 }
182             }
183         }
184         return paths;
185     }
186 
187     /**
188      * Wrapper for the zip input stream to prevent clients from closing it when reading entries
189      */
190     private static class UnclosableInputStream extends InputStream
191     {
192         private final InputStream delegate;
193 
194         public UnclosableInputStream(InputStream delegate)
195         {
196             this.delegate = delegate;
197         }
198 
199         public int read() throws IOException
200         {
201             return delegate.read();
202         }
203 
204         @Override
205         public void close() throws IOException
206         {
207             // do nothing
208         }
209     }
210 
211     private String determineId(Document pluginDoc, String beanName, int iteration)
212     {
213         String id = beanName;
214         if (id == null)
215         {
216             id = "bean" + iteration;
217         }
218 
219         id = id.replaceAll("#", "LB");
220 
221         for (Object element : pluginDoc.getRootElement().elements("component-import"))
222         {
223             String key = ((Element) element).attributeValue("key");
224             if (id.equals(key))
225             {
226                 id += iteration;
227                 break;
228             }
229         }
230         return id;
231     }
232 }