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.service.impl;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.config.property.ConfigContext;
21  import org.kuali.rice.krad.service.MaintainableXMLConversionService;
22  import org.springframework.core.io.AbstractResource;
23  import org.springframework.core.io.ClassPathResource;
24  import org.springframework.core.io.FileSystemResource;
25  import org.w3c.dom.Document;
26  import org.w3c.dom.Element;
27  import org.w3c.dom.Node;
28  import org.w3c.dom.NodeList;
29  import org.xml.sax.InputSource;
30  import org.xml.sax.SAXException;
31  
32  import javax.xml.parsers.DocumentBuilder;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  import javax.xml.parsers.ParserConfigurationException;
35  import javax.xml.transform.OutputKeys;
36  import javax.xml.transform.Transformer;
37  import javax.xml.transform.TransformerConfigurationException;
38  import javax.xml.transform.TransformerException;
39  import javax.xml.transform.TransformerFactory;
40  import javax.xml.transform.dom.DOMSource;
41  import javax.xml.transform.stream.StreamResult;
42  import javax.xml.xpath.XPath;
43  import javax.xml.xpath.XPathConstants;
44  import javax.xml.xpath.XPathExpression;
45  import javax.xml.xpath.XPathExpressionException;
46  import javax.xml.xpath.XPathFactory;
47  import java.io.IOException;
48  import java.io.StringReader;
49  import java.io.StringWriter;
50  import java.lang.reflect.InvocationTargetException;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.HashMap;
54  import java.util.List;
55  import java.util.Map;
56  
57  public class MaintainableXMLConversionServiceImpl implements MaintainableXMLConversionService {
58  
59      private static final String CONVERSION_RULE_FILE_PARAMETER = "maintainable.conversion.rule.file";
60      private static final String SERIALIZATION_ATTRIBUTE = "serialization";
61      private static final String CLASS_ATTRIBUTE = "class";
62      private static final String MAINTENANCE_ACTION_ELEMENT_NAME = "maintenanceAction";
63  
64      private Map<String, String> classNameRuleMap;
65      private Map<String, Map<String, String>> classPropertyRuleMap;
66      private String conversionRuleFile;
67  
68      public MaintainableXMLConversionServiceImpl() {
69          String conversionRuleFile = ConfigContext.getCurrentContextConfig().getProperty(CONVERSION_RULE_FILE_PARAMETER);
70          this.setConversionRuleFile(conversionRuleFile);
71      }
72  
73      @Override
74      public String transformMaintainableXML(String xml) {
75          String maintenanceAction = "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">" + StringUtils.substringAfter("<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">", xml);
76          xml = StringUtils.substringBefore(xml, "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">");
77          if(StringUtils.isNotBlank(this.getConversionRuleFile())) {
78              try {
79                  this.setRuleMaps();
80                  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
81                  DocumentBuilder db = dbf.newDocumentBuilder();
82                  Document document = db.parse(new InputSource(new StringReader(xml)));
83                  for(Node childNode = document.getFirstChild(); childNode != null;) {
84                      Node nextChild = childNode.getNextSibling();
85                      transformClassNode(document, childNode);
86                      childNode = nextChild;
87                  }
88                  TransformerFactory transFactory = TransformerFactory.newInstance();
89                  Transformer trans = transFactory.newTransformer();
90                  trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
91                  trans.setOutputProperty(OutputKeys.INDENT, "yes");
92  
93                  StringWriter writer = new StringWriter();
94                  StreamResult result = new StreamResult(writer);
95                  DOMSource source = new DOMSource(document);
96                  trans.transform(source, result);
97                  xml = writer.toString().replaceAll("(?m)^\\s+\\n", "");
98              } catch (ParserConfigurationException e) {
99                  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