001/** 002 * Copyright 2005-2015 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.service.impl; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.config.property.ConfigContext; 021import org.kuali.rice.krad.service.MaintainableXMLConversionService; 022import org.springframework.core.io.AbstractResource; 023import org.springframework.core.io.ClassPathResource; 024import org.springframework.core.io.FileSystemResource; 025import org.w3c.dom.Document; 026import org.w3c.dom.Element; 027import org.w3c.dom.Node; 028import org.w3c.dom.NodeList; 029import org.xml.sax.InputSource; 030import org.xml.sax.SAXException; 031 032import javax.xml.parsers.DocumentBuilder; 033import javax.xml.parsers.DocumentBuilderFactory; 034import javax.xml.parsers.ParserConfigurationException; 035import javax.xml.transform.OutputKeys; 036import javax.xml.transform.Transformer; 037import javax.xml.transform.TransformerConfigurationException; 038import javax.xml.transform.TransformerException; 039import javax.xml.transform.TransformerFactory; 040import javax.xml.transform.dom.DOMSource; 041import javax.xml.transform.stream.StreamResult; 042import javax.xml.xpath.XPath; 043import javax.xml.xpath.XPathConstants; 044import javax.xml.xpath.XPathExpression; 045import javax.xml.xpath.XPathExpressionException; 046import javax.xml.xpath.XPathFactory; 047import java.io.IOException; 048import java.io.StringReader; 049import java.io.StringWriter; 050import java.lang.reflect.InvocationTargetException; 051import java.util.ArrayList; 052import java.util.Collection; 053import java.util.HashMap; 054import java.util.List; 055import java.util.Map; 056 057public class MaintainableXMLConversionServiceImpl implements MaintainableXMLConversionService { 058 059 private static final String CONVERSION_RULE_FILE_PARAMETER = "maintainable.conversion.rule.file"; 060 private static final String SERIALIZATION_ATTRIBUTE = "serialization"; 061 private static final String CLASS_ATTRIBUTE = "class"; 062 private static final String MAINTENANCE_ACTION_ELEMENT_NAME = "maintenanceAction"; 063 064 private Map<String, String> classNameRuleMap; 065 private Map<String, Map<String, String>> classPropertyRuleMap; 066 private String conversionRuleFile; 067 068 public MaintainableXMLConversionServiceImpl() { 069 String conversionRuleFile = ConfigContext.getCurrentContextConfig().getProperty(CONVERSION_RULE_FILE_PARAMETER); 070 this.setConversionRuleFile(conversionRuleFile); 071 } 072 073 @Override 074 public String transformMaintainableXML(String xml) { 075 String maintenanceAction = "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">" + StringUtils.substringAfter("<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">", xml); 076 xml = StringUtils.substringBefore(xml, "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">"); 077 if(StringUtils.isNotBlank(this.getConversionRuleFile())) { 078 try { 079 this.setRuleMaps(); 080 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 081 DocumentBuilder db = dbf.newDocumentBuilder(); 082 Document document = db.parse(new InputSource(new StringReader(xml))); 083 for(Node childNode = document.getFirstChild(); childNode != null;) { 084 Node nextChild = childNode.getNextSibling(); 085 transformClassNode(document, childNode); 086 childNode = nextChild; 087 } 088 TransformerFactory transFactory = TransformerFactory.newInstance(); 089 Transformer trans = transFactory.newTransformer(); 090 trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 091 trans.setOutputProperty(OutputKeys.INDENT, "yes"); 092 093 StringWriter writer = new StringWriter(); 094 StreamResult result = new StreamResult(writer); 095 DOMSource source = new DOMSource(document); 096 trans.transform(source, result); 097 xml = writer.toString().replaceAll("(?m)^\\s+\\n", ""); 098 } catch (ParserConfigurationException e) { 099 e.printStackTrace(); 100 } catch (SAXException e) { 101 e.printStackTrace(); 102 } catch (IOException e) { 103 e.printStackTrace(); 104 } catch (ClassNotFoundException e) { 105 e.printStackTrace(); 106 } catch (TransformerConfigurationException e) { 107 e.printStackTrace(); 108 } catch (TransformerException e) { 109 e.printStackTrace(); 110 } catch (XPathExpressionException e) { 111 e.printStackTrace(); 112 } catch (IllegalAccessException e) { 113 e.printStackTrace(); 114 } catch (InvocationTargetException e) { 115 e.printStackTrace(); 116 } catch (NoSuchMethodException e) { 117 e.printStackTrace(); 118 } catch (InstantiationException e) { 119 e.printStackTrace(); 120 } 121 } 122 if(StringUtils.contains(xml, "edu.iu.uis.dp.bo.DataManager") || StringUtils.contains(xml, "edu.iu.uis.dp.bo.DataSteward")){ 123 xml = StringUtils.replace(xml, "org.kuali.rice.kim.bo.impl.PersonImpl", "org.kuali.rice.kim.impl.identity.PersonImpl"); 124 xml = xml.replaceAll("<autoIncrementSet.+", ""); 125 xml = xml.replaceAll("<address.+",""); 126 } 127 return xml + maintenanceAction; 128 } 129 130 public String getConversionRuleFile() { 131 return conversionRuleFile; 132 } 133 134 public void setConversionRuleFile(String conversionRuleFile) { 135 this.conversionRuleFile = conversionRuleFile; 136 } 137 138 private void transformClassNode(Document document, Node node) throws ClassNotFoundException, XPathExpressionException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException { 139 String className = node.getNodeName(); 140 if(this.classNameRuleMap.containsKey(className)) { 141 String newClassName = this.classNameRuleMap.get(className); 142 document.renameNode(node, null, newClassName); 143 className = newClassName; 144 } 145 Class<?> dataObjectClass = Class.forName(className); 146 if(classPropertyRuleMap.containsKey(className)) { 147 transformNode(document, node, dataObjectClass, classPropertyRuleMap.get(className)); 148 } 149 transformNode(document, node, dataObjectClass, classPropertyRuleMap.get("*")); 150 } 151 152 private void transformNode(Document document, Node node, Class<?> currentClass, Map<String, String> propertyMappings) throws ClassNotFoundException, XPathExpressionException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException { 153 for(Node childNode = node.getFirstChild(); childNode != null;) { 154 Node nextChild = childNode.getNextSibling(); 155 String propertyName = childNode.getNodeName(); 156 if(childNode.hasAttributes()) { 157 XPath xpath = XPathFactory.newInstance().newXPath(); 158 Node serializationAttribute = childNode.getAttributes().getNamedItem(SERIALIZATION_ATTRIBUTE); 159 if(serializationAttribute != null && StringUtils.equals(serializationAttribute.getNodeValue(), "custom")) { 160 Node classAttribute = childNode.getAttributes().getNamedItem(CLASS_ATTRIBUTE); 161 if(classAttribute != null && StringUtils.equals(classAttribute.getNodeValue(), "org.kuali.rice.kns.util.TypedArrayList")) { 162 ((Element)childNode).removeAttribute(SERIALIZATION_ATTRIBUTE); 163 ((Element)childNode).removeAttribute(CLASS_ATTRIBUTE); 164 XPathExpression listSizeExpression = xpath.compile("//" + propertyName + "/org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl/default/size/text()"); 165 String size = (String)listSizeExpression.evaluate(childNode, XPathConstants.STRING); 166 List<Node> nodesToAdd = new ArrayList<Node>(); 167 if(StringUtils.isNotBlank(size) && Integer.valueOf(size) > 0) { 168 XPathExpression listTypeExpression = xpath.compile("//" + propertyName + "/org.kuali.rice.kns.util.TypedArrayList/default/listObjectType/text()"); 169 String listType = (String)listTypeExpression.evaluate(childNode, XPathConstants.STRING); 170 XPathExpression listContentsExpression = xpath.compile("//" + propertyName + "/org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl/" + listType); 171 NodeList listContents = (NodeList)listContentsExpression.evaluate(childNode, XPathConstants.NODESET); 172 for(int i = 0; i < listContents.getLength(); i++) { 173 Node tempNode = listContents.item(i); 174 transformClassNode(document, tempNode); 175 nodesToAdd.add(tempNode); 176 } 177 } 178 for(Node removeNode = childNode.getFirstChild(); removeNode != null;) { 179 Node nextRemoveNode = removeNode.getNextSibling(); 180 childNode.removeChild(removeNode); 181 removeNode = nextRemoveNode; 182 } 183 for(Node nodeToAdd : nodesToAdd) { 184 childNode.appendChild(nodeToAdd); 185 } 186 } else { 187 ((Element)childNode).removeAttribute(SERIALIZATION_ATTRIBUTE); 188 189 XPathExpression mapContentsExpression = xpath.compile("//" + propertyName + "/map/string"); 190 NodeList mapContents = (NodeList)mapContentsExpression.evaluate(childNode, XPathConstants.NODESET); 191 List<Node> nodesToAdd = new ArrayList<Node>(); 192 if(mapContents.getLength() > 0 && mapContents.getLength() % 2 == 0) { 193 for(int i = 0; i < mapContents.getLength(); i++) { 194 Node keyNode = mapContents.item(i); 195 Node valueNode = mapContents.item(++i); 196 Node entryNode = document.createElement("entry"); 197 entryNode.appendChild(keyNode); 198 entryNode.appendChild(valueNode); 199 nodesToAdd.add(entryNode); 200 } 201 } 202 for(Node removeNode = childNode.getFirstChild(); removeNode != null;) { 203 Node nextRemoveNode = removeNode.getNextSibling(); 204 childNode.removeChild(removeNode); 205 removeNode = nextRemoveNode; 206 } 207 for(Node nodeToAdd : nodesToAdd) { 208 childNode.appendChild(nodeToAdd); 209 } 210 } 211 } 212 } 213 if(propertyMappings != null && propertyMappings.containsKey(propertyName)) { 214 String newPropertyName = propertyMappings.get(propertyName); 215 if(StringUtils.isNotBlank(newPropertyName)) { 216 document.renameNode(childNode, null, newPropertyName); 217 propertyName = newPropertyName; 218 } else { 219 // If there is no replacement name then the element needs 220 // to be removed and skip all other processing 221 node.removeChild(childNode); 222 childNode = nextChild; 223 continue; 224 } 225 } 226 if(childNode.hasChildNodes() && !(Collection.class.isAssignableFrom(currentClass) || Map.class.isAssignableFrom(currentClass))) { 227 if(propertyName.equals("principalId") && (node.getNodeName().equals("dataManagerUser") || node.getNodeName().equals("dataStewardUser"))){ 228 currentClass = new org.kuali.rice.kim.impl.identity.PersonImpl().getClass(); 229 } 230 Class<?> propertyClass = PropertyUtils.getPropertyType(currentClass.newInstance(), propertyName); 231 if(propertyClass != null && classPropertyRuleMap.containsKey(propertyClass.getName())) { 232 transformNode(document, childNode, propertyClass, this.classPropertyRuleMap.get(propertyClass.getName())); 233 } 234 transformNode(document, childNode, propertyClass, classPropertyRuleMap.get("*")); 235 } 236 childNode = nextChild; 237 } 238 } 239 240 private void setRuleMaps() { 241 setupConfigurationMaps(); 242 try { 243 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 244 DocumentBuilder db = dbf.newDocumentBuilder(); 245 246 AbstractResource resource = null; 247 Document doc = null; 248 if(StringUtils.startsWith(this.getConversionRuleFile(), "classpath")) { 249 resource = new ClassPathResource(this.getConversionRuleFile(), Thread.currentThread().getContextClassLoader()); 250 } else { 251 resource = new FileSystemResource(this.getConversionRuleFile()); 252 } 253 if(!resource.exists()) { 254 doc = db.parse(this.getClass().getResourceAsStream(this.getConversionRuleFile())); 255 } else { 256 doc = db.parse(resource.getInputStream()); 257 } 258 doc.getDocumentElement().normalize(); 259 XPath xpath = XPathFactory.newInstance().newXPath(); 260 261 // Get the moved classes rules 262 XPathExpression exprClassNames = xpath.compile("//*[@name='maint_doc_classname_changes']/pattern"); 263 NodeList classNamesList = (NodeList) exprClassNames.evaluate(doc, XPathConstants.NODESET); 264 for (int s = 0; s < classNamesList.getLength(); s++) { 265 String matchText = xpath.evaluate("match/text()", classNamesList.item(s)); 266 String replaceText = xpath.evaluate("replacement/text()", classNamesList.item(s)); 267 classNameRuleMap.put(matchText, replaceText); 268 } 269 270 // Get the property changed rules 271 272 XPathExpression exprClassProperties = xpath.compile( 273 "//*[@name='maint_doc_changed_class_properties']/pattern"); 274 XPathExpression exprClassPropertiesPatterns = xpath.compile("pattern"); 275 NodeList propertyClassList = (NodeList) exprClassProperties.evaluate(doc, XPathConstants.NODESET); 276 for (int s = 0; s < propertyClassList.getLength(); s++) { 277 String classText = xpath.evaluate("class/text()", propertyClassList.item(s)); 278 Map<String, String> propertyRuleMap = new HashMap<String, String>(); 279 NodeList classPropertiesPatterns = (NodeList) exprClassPropertiesPatterns.evaluate( 280 propertyClassList.item(s), XPathConstants.NODESET); 281 for (int c = 0; c < classPropertiesPatterns.getLength(); c++) { 282 String matchText = xpath.evaluate("match/text()", classPropertiesPatterns.item(c)); 283 String replaceText = xpath.evaluate("replacement/text()", classPropertiesPatterns.item(c)); 284 propertyRuleMap.put(matchText, replaceText); 285 } 286 classPropertyRuleMap.put(classText, propertyRuleMap); 287 } 288 } catch (Exception e) { 289 System.out.println("Error parsing rule xml file. Please check file. : " + e.getMessage()); 290 e.printStackTrace(); 291 } 292 } 293 294 private void setupConfigurationMaps() { 295 classNameRuleMap = new HashMap<String, String>(); 296 classPropertyRuleMap = new HashMap<String, Map<String,String>>(); 297 298 // Pre-populate the class property rules with some defaults which apply to every BO 299 Map<String, String> defaultPropertyRules = new HashMap<String, String>(); 300 defaultPropertyRules.put("boNotes", ""); 301 defaultPropertyRules.put("autoIncrementSet", ""); 302 classPropertyRuleMap.put("*", defaultPropertyRules); 303 } 304} 305