View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.devtools.maintainablexml;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.springframework.core.io.AbstractResource;
21  import org.w3c.dom.Document;
22  import org.w3c.dom.Element;
23  import org.w3c.dom.Node;
24  import org.w3c.dom.NodeList;
25  import org.xml.sax.InputSource;
26  
27  import javax.xml.parsers.DocumentBuilder;
28  import javax.xml.parsers.DocumentBuilderFactory;
29  import javax.xml.transform.OutputKeys;
30  import javax.xml.transform.Transformer;
31  import javax.xml.transform.TransformerFactory;
32  import javax.xml.transform.dom.DOMSource;
33  import javax.xml.transform.stream.StreamResult;
34  import javax.xml.xpath.XPath;
35  import javax.xml.xpath.XPathConstants;
36  import javax.xml.xpath.XPathExpression;
37  import javax.xml.xpath.XPathExpressionException;
38  import javax.xml.xpath.XPathFactory;
39  import java.io.StringReader;
40  import java.io.StringWriter;
41  import java.lang.reflect.InvocationTargetException;
42  import java.util.ArrayList;
43  import java.util.Collection;
44  import java.util.HashMap;
45  import java.util.List;
46  import java.util.Map;
47  
48  public class MaintainableXMLConversionServiceImpl {
49  
50  	private static final String SERIALIZATION_ATTRIBUTE = "serialization";
51  	private static final String CLASS_ATTRIBUTE = "class";
52  	private static final String MAINTENANCE_ACTION_ELEMENT_NAME = "maintenanceAction";
53      private static final String OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME = "oldMaintainableObject";
54      private static final String NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME = "newMaintainableObject";
55  
56      private Map<String, String> classNameRuleMap;
57  	private Map<String, Map<String, String>> classPropertyRuleMap;
58      private Map<String, String> dateRuleMap;
59  	public MaintainableXMLConversionServiceImpl() {
60  	}
61  
62  	public String transformMaintainableXML(String xml) {
63  	    String beginning = StringUtils.substringBefore(xml, "<" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
64          String oldMaintainableObjectXML = StringUtils.substringBetween(xml, "<" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">", "</" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
65          String newMaintainableObjectXML = StringUtils.substringBetween(xml, "<" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">", "</" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
66          String ending = StringUtils.substringAfter(xml, "</" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
67  
68          String convertedOldMaintainableObjectXML = transformSection(oldMaintainableObjectXML);
69          String convertedNewMaintainableObjectXML = transformSection(newMaintainableObjectXML);
70  
71          String convertedXML =  beginning +
72              "<" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" + convertedOldMaintainableObjectXML +  "</" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" +
73              "<" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" + convertedNewMaintainableObjectXML +  "</" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" +
74              ending;
75          return convertedXML;
76  	}
77  
78      private String transformSection(String xml) {
79  
80          String maintenanceAction = StringUtils.substringBetween(xml, "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">", "</" + MAINTENANCE_ACTION_ELEMENT_NAME + ">");
81          xml = StringUtils.substringBefore(xml, "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">");
82  
83          try {
84              xml = upgradeBONotes(xml);
85              if (classNameRuleMap == null) {
86                  setRuleMaps();
87              }
88  
89              DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
90              DocumentBuilder db = dbf.newDocumentBuilder();
91              Document document = db.parse(new InputSource(new StringReader(xml)));
92  
93              removePersonObjects(document);
94  
95              for(Node childNode = document.getFirstChild(); childNode != null;) {
96                  Node nextChild = childNode.getNextSibling();
97                  transformClassNode(document, childNode);
98                  childNode = nextChild;
99              }
100 
101             TransformerFactory transFactory = TransformerFactory.newInstance();
102             Transformer trans = transFactory.newTransformer();
103             trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
104             trans.setOutputProperty(OutputKeys.INDENT, "yes");
105 
106             StringWriter writer = new StringWriter();
107             StreamResult result = new StreamResult(writer);
108             DOMSource source = new DOMSource(document);
109             trans.transform(source, result);
110             xml = writer.toString().replaceAll("(?m)^\\s+\\n", "");
111         } catch (Exception e) {
112             e.printStackTrace();
113         }
114 
115         return xml + "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">" + maintenanceAction + "</" + MAINTENANCE_ACTION_ELEMENT_NAME + ">";
116     }
117 
118 
119     /**
120      * Upgrades the old Bo notes tag that was part of the maintainable to the new notes tag.
121      *
122      * @param oldXML - the xml to upgrade
123      * @throws Exception
124      */
125     private String upgradeBONotes(String oldXML) throws Exception {
126         // Get the old bo note xml
127         String notesXml = StringUtils.substringBetween(oldXML, "<boNotes>", "</boNotes>");
128         if (notesXml != null) {
129             notesXml = notesXml.replace("org.kuali.rice.kns.bo.Note", "org.kuali.rice.krad.bo.Note");
130             notesXml = "<org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl>\n"
131                     + notesXml
132                     + "\n</org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl>";
133             oldXML = oldXML.replaceFirst(">", ">\n<notes>\n" + notesXml + "\n</notes>");
134         }
135         return oldXML;
136     }
137 
138     public void removePersonObjects( Document doc ) {
139         XPath xpath = XPathFactory.newInstance().newXPath();
140         XPathExpression personProperties = null;
141         try {
142             personProperties = xpath.compile("//*[@class='org.kuali.rice.kim.impl.identity.PersonImpl']");
143             NodeList matchingNodes = (NodeList)personProperties.evaluate( doc, XPathConstants.NODESET );
144             for(int i = 0; i < matchingNodes.getLength(); i++) {
145                 Node tempNode = matchingNodes.item(i);
146                 tempNode.getParentNode().removeChild(tempNode);
147             }
148         } catch (XPathExpressionException e) {
149             e.printStackTrace();
150         }
151     }
152 
153     private void transformClassNode(Document document, Node node) throws ClassNotFoundException, XPathExpressionException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException {
154 		String className = node.getNodeName();
155 		if(this.classNameRuleMap.containsKey(className)) {
156 			String newClassName = this.classNameRuleMap.get(className);
157 			document.renameNode(node, null, newClassName);
158 			className = newClassName;
159 		}
160 	    Class<?> dataObjectClass = Class.forName(className);
161 		if(classPropertyRuleMap.containsKey(className)) {
162 			transformNode(document, node, dataObjectClass, classPropertyRuleMap.get(className));
163 		}
164 		transformNode(document, node, dataObjectClass, classPropertyRuleMap.get("*"));
165 	}
166 
167 	private void transformNode(Document document, Node node, Class<?> currentClass, Map<String, String> propertyMappings) throws ClassNotFoundException, XPathExpressionException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException {
168 		for(Node childNode = node.getFirstChild(); childNode != null;) {
169 			Node nextChild = childNode.getNextSibling();
170 			String propertyName = childNode.getNodeName();
171 			if(childNode.hasAttributes()) {
172 				XPath xpath = XPathFactory.newInstance().newXPath();
173 				Node serializationAttribute = childNode.getAttributes().getNamedItem(SERIALIZATION_ATTRIBUTE);
174 				if(serializationAttribute != null && StringUtils.equals(serializationAttribute.getNodeValue(), "custom")) {
175 					Node classAttribute = childNode.getAttributes().getNamedItem(CLASS_ATTRIBUTE);
176 					if(classAttribute != null && StringUtils.equals(classAttribute.getNodeValue(), "org.kuali.rice.kns.util.TypedArrayList")) {
177 						((Element)childNode).removeAttribute(SERIALIZATION_ATTRIBUTE);
178 						((Element)childNode).removeAttribute(CLASS_ATTRIBUTE);
179 						XPathExpression listSizeExpression = xpath.compile("//" + propertyName + "/org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl/default/size/text()");
180 						String size = (String)listSizeExpression.evaluate(childNode, XPathConstants.STRING);
181 						List<Node> nodesToAdd = new ArrayList<Node>();
182 						if(StringUtils.isNotBlank(size) && Integer.valueOf(size) > 0) {
183 							XPathExpression listTypeExpression = xpath.compile("//" + propertyName + "/org.kuali.rice.kns.util.TypedArrayList/default/listObjectType/text()");
184 							String listType = (String)listTypeExpression.evaluate(childNode, XPathConstants.STRING);
185 							XPathExpression listContentsExpression = xpath.compile("//" + propertyName + "/org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl/" + listType);
186 							NodeList listContents = (NodeList)listContentsExpression.evaluate(childNode, XPathConstants.NODESET);
187 							for(int i = 0; i < listContents.getLength(); i++) {
188 								Node tempNode = listContents.item(i);
189 								transformClassNode(document, tempNode);
190 								nodesToAdd.add(tempNode);
191 							}
192 						}
193 						for(Node removeNode = childNode.getFirstChild(); removeNode != null;) {
194 							Node nextRemoveNode = removeNode.getNextSibling();
195 							childNode.removeChild(removeNode);
196 							removeNode = nextRemoveNode;
197 						}
198 						for(Node nodeToAdd : nodesToAdd) {
199 							childNode.appendChild(nodeToAdd);
200 						}
201 					} else {
202 						((Element)childNode).removeAttribute(SERIALIZATION_ATTRIBUTE);
203 						
204 						XPathExpression mapContentsExpression = xpath.compile("//" + propertyName + "/map/string");
205 						NodeList mapContents = (NodeList)mapContentsExpression.evaluate(childNode, XPathConstants.NODESET);
206 						List<Node> nodesToAdd = new ArrayList<Node>();
207 						if(mapContents.getLength() > 0 && mapContents.getLength() % 2 == 0) {
208 							for(int i = 0; i < mapContents.getLength(); i++) {
209 								Node keyNode = mapContents.item(i);
210 								Node valueNode = mapContents.item(++i);
211 								Node entryNode = document.createElement("entry");
212 								entryNode.appendChild(keyNode);
213 								entryNode.appendChild(valueNode);
214 								nodesToAdd.add(entryNode);
215 							}
216 						}
217 						for(Node removeNode = childNode.getFirstChild(); removeNode != null;) {
218 							Node nextRemoveNode = removeNode.getNextSibling();
219 							childNode.removeChild(removeNode);
220 							removeNode = nextRemoveNode;
221 						}
222 						for(Node nodeToAdd : nodesToAdd) {
223 							childNode.appendChild(nodeToAdd);
224 						}
225 					}
226 				}
227 			}
228 			if(propertyMappings != null && propertyMappings.containsKey(propertyName)) {
229 				String newPropertyName = propertyMappings.get(propertyName);
230 				if(StringUtils.isNotBlank(newPropertyName)) {
231 					document.renameNode(childNode, null, newPropertyName);
232 					propertyName = newPropertyName;
233 				} else {
234 					// If there is no replacement name then the element needs
235 					// to be removed and skip all other processing
236 					node.removeChild(childNode);
237 					childNode = nextChild;
238 					continue;
239 				}
240 			}
241 
242             if(dateRuleMap != null && dateRuleMap.containsKey(propertyName)) {
243                 String newDateValue = dateRuleMap.get(propertyName);
244                 if(StringUtils.isNotBlank(newDateValue)) {
245                     if ( childNode.getTextContent().length() == 10 ) {
246                         childNode.setTextContent( childNode.getTextContent() + " " + newDateValue );
247 
248                     }
249                 }
250             }
251 
252             if (currentClass != null) {
253                 if (childNode.hasChildNodes() && !(Collection.class.isAssignableFrom(currentClass) || Map.class
254                         .isAssignableFrom(currentClass))) {
255                     Class<?> propertyClass = PropertyUtils.getPropertyType(currentClass.newInstance(), propertyName);
256                     if (propertyClass != null && classPropertyRuleMap.containsKey(propertyClass.getName())) {
257                         transformNode(document, childNode, propertyClass, this.classPropertyRuleMap.get(
258                                 propertyClass.getName()));
259                     }
260                     transformNode(document, childNode, propertyClass, classPropertyRuleMap.get("*"));
261                 }
262             }
263 			childNode = nextChild;
264 		}
265 	}
266 
267     /**
268      * Reads the rule xml and sets up the rule maps that will be used to transform the xml
269      */
270 	private void setRuleMaps() {
271 		setupConfigurationMaps();
272 		try {
273 			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
274 			DocumentBuilder db = dbf.newDocumentBuilder();
275 			
276 			AbstractResource resource = null;
277 
278             Document doc = db.parse(getClass().getResourceAsStream(
279                     "/org/kuali/rice/devtools/krad/maintainablexml/MaintainableXMLUpgradeRules.xml"));
280 
281 			doc.getDocumentElement().normalize();
282 			XPath xpath = XPathFactory.newInstance().newXPath();
283 
284 			// Get the moved classes rules
285 			XPathExpression exprClassNames = xpath.compile("//*[@name='maint_doc_classname_changes']/pattern");
286 			NodeList classNamesList = (NodeList) exprClassNames.evaluate(doc, XPathConstants.NODESET);
287 			for (int s = 0; s < classNamesList.getLength(); s++) {
288 				String matchText = xpath.evaluate("match/text()", classNamesList.item(s));
289 				String replaceText = xpath.evaluate("replacement/text()", classNamesList.item(s));
290 				classNameRuleMap.put(matchText, replaceText);
291 			}
292 
293 			// Get the property changed rules
294 
295 			XPathExpression exprClassProperties = xpath.compile(
296 					"//*[@name='maint_doc_changed_class_properties']/pattern");
297 			XPathExpression exprClassPropertiesPatterns = xpath.compile("pattern");
298 			NodeList propertyClassList = (NodeList) exprClassProperties.evaluate(doc, XPathConstants.NODESET);
299 			for (int s = 0; s < propertyClassList.getLength(); s++) {
300 				String classText = xpath.evaluate("class/text()", propertyClassList.item(s));
301 				Map<String, String> propertyRuleMap = new HashMap<String, String>();
302 				NodeList classPropertiesPatterns = (NodeList) exprClassPropertiesPatterns.evaluate(
303 						propertyClassList.item(s), XPathConstants.NODESET);
304 				for (int c = 0; c < classPropertiesPatterns.getLength(); c++) {
305 					String matchText = xpath.evaluate("match/text()", classPropertiesPatterns.item(c));
306 					String replaceText = xpath.evaluate("replacement/text()", classPropertiesPatterns.item(c));
307 					propertyRuleMap.put(matchText, replaceText);
308 				}
309 				classPropertyRuleMap.put(classText, propertyRuleMap);
310 			}
311 
312             // Get the Date rules
313             XPathExpression dateFieldNames = xpath.compile("//*[@name='maint_doc_date_changes']/pattern");
314             NodeList DateNamesList = (NodeList) dateFieldNames.evaluate(doc, XPathConstants.NODESET);
315             for (int s = 0; s < DateNamesList.getLength(); s++) {
316                 String matchText = xpath.evaluate("match/text()", DateNamesList.item(s));
317                 String replaceText = xpath.evaluate("replacement/text()", DateNamesList.item(s));
318                 dateRuleMap.put(matchText, replaceText);
319             }
320 		} catch (Exception e) {
321 			System.out.println("Error parsing rule xml file. Please check file. : " + e.getMessage());
322 			e.printStackTrace();
323 		}
324 	}
325 	
326 	private void setupConfigurationMaps() {
327 		classNameRuleMap = new HashMap<String, String>();
328 		classPropertyRuleMap = new HashMap<String, Map<String,String>>();
329         dateRuleMap = new HashMap<String, String>();
330 
331         // Pre-populate the class property rules with some defaults which apply to every BO
332 		Map<String, String> defaultPropertyRules = new HashMap<String, String>();
333 		defaultPropertyRules.put("boNotes", "");
334 		defaultPropertyRules.put("autoIncrementSet", "");
335         classPropertyRuleMap.put("*", defaultPropertyRules);
336 	}
337 }