1 package com.atlassian.plugins.codegen;
2
3 import java.io.File;
4 import java.io.FileOutputStream;
5 import java.io.IOException;
6 import java.io.OutputStreamWriter;
7 import java.util.List;
8 import java.util.Map;
9
10 import com.atlassian.plugins.codegen.modules.PluginModuleLocation;
11 import com.atlassian.plugins.codegen.util.PluginXmlHelper;
12
13 import com.google.common.collect.ImmutableList;
14
15 import org.apache.commons.io.IOUtils;
16 import org.dom4j.Document;
17 import org.dom4j.DocumentException;
18 import org.dom4j.Element;
19 import org.dom4j.Node;
20 import org.dom4j.io.OutputFormat;
21 import org.dom4j.io.XMLWriter;
22
23 import static com.google.common.collect.Iterables.concat;
24
25
26
27
28
29 public class PluginXmlRewriter implements ProjectRewriter
30 {
31 private final PluginXmlHelper xmlHelper;
32 private final Document document;
33
34 public PluginXmlRewriter(PluginModuleLocation location) throws IOException, DocumentException
35 {
36 this.xmlHelper = new PluginXmlHelper(location);
37 this.document = xmlHelper.getDocument();
38 }
39
40 public PluginXmlRewriter(File pluginXmlFile) throws IOException, DocumentException
41 {
42 this.xmlHelper = new PluginXmlHelper(pluginXmlFile);
43 this.document = xmlHelper.getDocument();
44 }
45
46 @Override
47 public void applyChanges(PluginProjectChangeset changes) throws IOException
48 {
49 boolean modified = false;
50
51 try
52 {
53 if (changes.hasItems(I18nString.class))
54 {
55 modified |= addI18nResource(xmlHelper.getDefaultI18nName(), xmlHelper.getDefaultI18nLocation());
56 }
57
58 for (PluginParameter pluginParam : changes.getItems(PluginParameter.class))
59 {
60 modified |= addPluginInfoParam(pluginParam.getName(), pluginParam.getValue());
61 }
62
63 for (ComponentImport componentImport : changes.getItems(ComponentImport.class))
64 {
65 modified |= addComponentImport(componentImport);
66 }
67
68 for (ComponentDeclaration component : changes.getItems(ComponentDeclaration.class))
69 {
70 modified |= addComponentDeclaration(component);
71 }
72
73 for (ModuleDescriptor moduleDescriptor : changes.getItems(ModuleDescriptor.class))
74 {
75 modified |= addModuleAsLastChild(moduleDescriptor.getContent());
76 }
77 }
78 catch (DocumentException e)
79 {
80 throw new IOException(e);
81 }
82
83 if (modified)
84 {
85 OutputFormat format = OutputFormat.createPrettyPrint();
86 FileOutputStream fos = new FileOutputStream(xmlHelper.getXmlFile());
87 OutputStreamWriter osw = new OutputStreamWriter(fos);
88 XMLWriter writer = new XMLWriter(osw, format);
89 try
90 {
91 writer.write(document);
92 }
93 finally
94 {
95 writer.close();
96 IOUtils.closeQuietly(osw);
97 IOUtils.closeQuietly(fos);
98 }
99 }
100 }
101
102 @SuppressWarnings("unchecked")
103 private Element createModule(String type)
104 {
105 Element newElement = document.getRootElement().addElement(type);
106 List<Element> existingModules = (List<Element>) document.getRootElement().elements(type);
107 if (!existingModules.isEmpty())
108 {
109 newElement.detach();
110 existingModules.add(newElement);
111 }
112 return newElement;
113 }
114
115 private boolean addModuleAsLastChild(Element module)
116 {
117 String key = module.attributeValue("key");
118 if ((key == null) || !PluginXmlHelper.findElementByTypeAndAttribute(document.getRootElement(), module.getName(), "key", key).isDefined())
119 {
120 Element pluginRoot = document.getRootElement();
121 pluginRoot.add(module);
122 return true;
123 }
124 return false;
125 }
126
127 private boolean addI18nResource(String name, String location) throws DocumentException, IOException
128 {
129 String xpath = "//resource[@type='i18n' and (@name = '" + name + "' or @location='" + location + "')]";
130 Node resourceNode = document.selectSingleNode(xpath);
131
132 if (resourceNode == null)
133 {
134 Element resource = document.getRootElement().addElement("resource");
135 resource.addAttribute("type", "i18n");
136 resource.addAttribute("name", name);
137 resource.addAttribute("location", location);
138 return true;
139 }
140
141 return false;
142 }
143
144 private boolean addPluginInfoParam(String name, String value)
145 {
146 Element pluginInfo = (Element) document.selectSingleNode("//plugin-info");
147 if (pluginInfo == null)
148 {
149 pluginInfo = document.addElement("plugin-info");
150 }
151 if (!PluginXmlHelper.findElementByTypeAndAttribute(pluginInfo, "param", "name", name).isDefined())
152 {
153 pluginInfo.addElement("param").addAttribute("name", name).setText(value);
154 return true;
155 }
156 return false;
157 }
158
159 private boolean addComponentImport(ComponentImport componentImport) throws DocumentException
160 {
161 String key = componentImport.getKey().getOrElse(createKeyFromClass(componentImport.getInterfaceClass()));
162 for (ClassId interfaceId : concat(ImmutableList.of(componentImport.getInterfaceClass()), componentImport.getAlternateInterfaces()))
163 {
164 if ((document.getRootElement().selectNodes("//component-import[@interface='" + interfaceId.getFullName() + "']").size() > 0)
165 || (document.getRootElement().selectNodes("//component-import/interface[text()='" + interfaceId.getFullName() + "']").size() > 0))
166 {
167 return false;
168 }
169 }
170 if (PluginXmlHelper.findElementByTypeAndAttribute(document.getRootElement(), "component-import", "key", key).isDefined())
171 {
172 return false;
173 }
174
175 Element element = createModule("component-import");
176 element.addAttribute("key", key);
177 element.addAttribute("interface", componentImport.getInterfaceClass().getFullName());
178 for (String filter : componentImport.getFilter())
179 {
180 element.addAttribute("filter", filter);
181 }
182 return true;
183 }
184
185 private boolean addComponentDeclaration(ComponentDeclaration component) throws DocumentException
186 {
187 if (!PluginXmlHelper.findElementByTypeAndAttribute(document.getRootElement(), "component", "key", component.getKey()).isDefined())
188 {
189 Element element = createModule("component");
190 element.addAttribute("key", component.getKey());
191 element.addAttribute("class", component.getClassId().getFullName());
192 for (String name : component.getName())
193 {
194 element.addAttribute("name", name);
195 }
196 for (String nameI18nKey : component.getNameI18nKey())
197 {
198 element.addAttribute("i18n-name-key", nameI18nKey);
199 }
200 if (component.getVisibility() == ComponentDeclaration.Visibility.PUBLIC)
201 {
202 element.addAttribute("public", "true");
203 }
204 for (String alias : component.getAlias())
205 {
206 element.addAttribute("alias", alias);
207 }
208 for (String description : component.getDescription())
209 {
210 Element eDesc = element.addElement("description");
211 eDesc.setText(description);
212 for (String descI18nKey : component.getDescriptionI18nKey())
213 {
214 eDesc.addAttribute("key", descI18nKey);
215 }
216 }
217 for (ClassId interfaceId : component.getInterfaceId())
218 {
219 element.addElement("interface").setText(interfaceId.getFullName());
220 }
221 if (!component.getServiceProperties().isEmpty())
222 {
223 Element eProps = element.addElement("service-properties");
224 for (Map.Entry<String, String> entry : component.getServiceProperties().entrySet())
225 {
226 Element eEntry = eProps.addElement("entry");
227 eEntry.addAttribute("key", entry.getKey());
228 eEntry.addAttribute("value", entry.getValue());
229 }
230 }
231 return true;
232 }
233 return false;
234 }
235
236 private String createKeyFromClass(ClassId classId)
237 {
238 return lowercaseFirst(classId.getName());
239 }
240
241 private String lowercaseFirst(String input)
242 {
243 return input.equals("") ? input : (input.substring(0, 1).toLowerCase() + input.substring(1));
244 }
245 }