001/**
002 * Copyright 2005-2017 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.kew.xml;
017
018import org.apache.commons.lang.StringEscapeUtils;
019import org.kuali.rice.core.api.util.ConcreteKeyValue;
020import org.kuali.rice.core.api.util.KeyValue;
021import org.xml.sax.Attributes;
022import org.xml.sax.SAXException;
023import org.xml.sax.helpers.XMLFilterImpl;
024
025import java.util.ArrayList;
026import java.util.List;
027
028/**
029 * This abstract class handles the xml stack of elements and makes
030 * it easier to run a transformation on certain elements.
031 * 
032 * @author Kuali Rice Team (rice.collab@kuali.org)
033 *
034 */
035public abstract class AbstractTransformationFilter extends XMLFilterImpl {
036
037        
038        // The list which helps keep track of where we are in the XML 
039        // hierarchy as the stream is being processed
040        private List<String> groupXmlStack = new ArrayList<String>();
041        
042        /**
043         * 
044         * This method allows you to modify the element passed in.  The returned element
045         * will be pushed into a "super.startElement(uri, localName, qName, atts)" call.
046         * 
047         * @param currentElement
048         * @return
049         */
050        public abstract CurrentElement transformStartElement(CurrentElement currentElement) throws SAXException;
051        
052        /**
053         * 
054         * This method allows you to modify the element passed in.  The returned element
055         * will be pushed into a "super.endElement(uri, localName, qName" call.
056         * 
057         * @param currentElement
058         * @return
059         */
060        public abstract CurrentElement transformEndElement(CurrentElement currentElement) throws SAXException;
061                
062        /*
063         * Build a Map that maps elements we intend to transform to their corresponding transformed value.
064         * The keys in this Map are "hierarchically-qualified" representations of the elements of concern.
065         * 
066         * For example, if "group" is a child of "groups", which is in turn a child of the root
067         * element "data", then it is represented as "data.groups.group" in the Map.
068         */
069        public abstract List<KeyValue> getElementTransformationList();  
070
071        /**
072         * 
073         * This method returns the element that we should start transforming at.
074         * So, if we had:
075         * <data>
076         *   <groups>
077         *     <group>
078         *     
079         * We might want to start transforming at the group level. 
080         * In that case the startingElement = "group"
081         * 
082         * @return
083         */
084        public abstract String getStartingElementPath();
085        
086        @Override
087        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
088                // Push the element onto the stack
089                if (groupXmlStack.isEmpty()){                   
090                                groupXmlStack.add(localName);                                   
091                }
092                else {
093                        // Push a child element by appending localName to the value of the top element in the stack
094                        groupXmlStack.add(groupXmlStack.get(groupXmlStack.size()-1) + "." + localName);
095                }
096                
097                // Fetch the current element from the top of the stack
098                String currentElementKey = groupXmlStack.get(groupXmlStack.size()-1);
099                CurrentElement currentElement = new CurrentElement(currentElementKey,uri, localName, qName, atts);
100                                
101                // Transform elements of concern:
102                if (getElementTransformationList().contains(new ConcreteKeyValue(getTrimmedCurrentElementKey(currentElementKey), uri))){
103                        CurrentElement transformedElement = this.transformStartElement(currentElement);                 
104                        super.startElement(transformedElement.getUri(), transformedElement.getLocalName(), transformedElement.getqName(), transformedElement.getAttributes());
105                }
106                else {
107                        // Pass other elements through as they are
108                        super.startElement(uri, localName, qName, atts);
109                }
110    }
111                
112        protected String getTrimmedCurrentElementKey(String currentElementKey){
113                return currentElementKey.replaceFirst(StringEscapeUtils.escapeJava(this.getStartingElementPath()+"."), "");
114        }
115    @Override
116        public void endElement(String uri, String localName, String qName) throws SAXException {
117                // Fetch the current element from the top of the stack
118                String currentElementKey = groupXmlStack.get(groupXmlStack.size()-1);
119                CurrentElement currentElement = new CurrentElement(currentElementKey,uri, localName, qName);
120                
121                if (getElementTransformationList().contains(new ConcreteKeyValue(getTrimmedCurrentElementKey(currentElementKey), uri))){
122                        CurrentElement transformedElement = this.transformEndElement(currentElement);                   
123                        super.endElement(transformedElement.getUri(), transformedElement.getLocalName(), transformedElement.getqName());
124                }               
125                else {
126                        // Pass other elements through as they are
127                        super.endElement(uri, localName, qName);
128                }
129                
130                // Pop the element from the stack if it's not empty
131                if (!groupXmlStack.isEmpty()){
132                        groupXmlStack.remove(currentElementKey);                        
133                }
134    }
135
136        public class CurrentElement {
137                String nameKey;
138                String uri;
139                String localName; 
140                String qName; 
141                Attributes attributes;
142                
143                public CurrentElement(){}
144                                
145                public CurrentElement(String nameKey, String uri, String localName,
146                                String qName) {
147                        super();
148                        this.nameKey = nameKey;
149                        this.uri = uri;
150                        this.localName = localName;
151                        this.qName = qName;
152                }
153
154                public CurrentElement(String nameKey,String uri, String localName, String qName,
155                                Attributes attributes) {
156                        super();
157                        this.nameKey = nameKey;
158                        this.uri = uri;
159                        this.localName = localName;
160                        this.qName = qName;
161                        this.attributes = attributes;
162                }
163
164
165                public String getUri() {
166                        return this.uri;
167                }
168                public void setUri(String uri) {
169                        this.uri = uri;
170                }
171                public String getLocalName() {
172                        return this.localName;
173                }
174                public void setLocalName(String localName) {
175                        this.localName = localName;
176                }
177                public String getqName() {
178                        return this.qName;
179                }
180                public void setqName(String qName) {
181                        this.qName = qName;
182                }
183                public Attributes getAttributes() {
184                        return this.attributes;
185                }
186                public void setAttributes(Attributes attributes) {
187                        this.attributes = attributes;
188                }
189
190                public String getNameKey() {
191                        return this.nameKey;
192                }
193
194                public void setNameKey(String nameKey) {
195                        this.nameKey = nameKey;
196                }               
197        }
198        
199}