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