001/**
002 * Copyright 2005-2018 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.datadictionary.validator;
017
018import org.apache.commons.logging.Log;
019import org.apache.commons.logging.LogFactory;
020import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean;
021import org.kuali.rice.krad.uif.component.Component;
022import org.kuali.rice.krad.uif.component.DataBinding;
023import org.springframework.core.io.ResourceLoader;
024import org.w3c.dom.Document;
025import org.w3c.dom.NodeList;
026
027import javax.xml.parsers.DocumentBuilder;
028import javax.xml.parsers.DocumentBuilderFactory;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.Map;
033
034/**
035 * Linear collection of identifiers for individual Spring Beans starting with the base bean and ending with the most
036 * recent.  Has the ability to located xml files related to the trace.
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040public class ValidationTrace {
041    private static final Log LOG = LogFactory.getLog(ValidationTrace.class);
042
043    // Constant identifer for a trace entry where the bean has no identifier itself
044    public static final String NO_BEAN_ID = "NOBEANID";
045
046    // Constant identifier for a trace during startup
047    public static final int START_UP = 0;
048
049    // Constant identifier for a trace during render
050    public static final int BUILD = 1;
051
052    private ArrayList<String> beanIds;
053    private ArrayList<String> beanTypes;
054    private Map<String, Document> beanMap;
055    private int validationStage;
056
057    /**
058     * Constructor for an empty token to start a trace
059     */
060    public ValidationTrace() {
061        beanIds = new ArrayList<String>();
062        beanTypes = new ArrayList<String>();
063        beanMap = new HashMap<String, Document>();
064    }
065
066    /**
067     * Constructor for an empty token to start a trace
068     * @param files files to load
069     * @param loader resource loader
070     */
071    public ValidationTrace(String[] files, ResourceLoader loader) {
072        beanIds = new ArrayList<String>();
073        beanTypes = new ArrayList<String>();
074        beanMap = new HashMap<String, Document>();
075        loadFiles(files, loader);
076    }
077
078    /**
079     * Adds a single entry into the trace
080     *
081     * @param beanId - An identifier for the bean
082     * @param beanType - The type of bean
083     */
084    public void addBean(String beanType, String beanId) {
085        beanIds.add(beanId);
086        beanTypes.add(beanType);
087    }
088
089    /**
090     * Adds a UIF Component to the trace
091     *
092     * @param component - The object to be added
093     */
094    public void addBean(Component component) {
095        String beanId = NO_BEAN_ID;
096        String beanType = component.getClass().getSimpleName();
097        if (component.getId() != null) {
098            if (component.getId().compareTo("null") != 0) {
099                beanId = component.getId();
100            } else {
101                try {
102                    beanId = ((DataBinding) component).getPropertyName();
103
104                } catch (Exception e) {
105                    beanId = NO_BEAN_ID;
106                }
107            }
108        } else {
109            try {
110                beanId = ((DataBinding) component).getPropertyName();
111            } catch (Exception e) {
112                beanId = NO_BEAN_ID;
113            }
114        }
115        addBean(beanType, beanId);
116    }
117
118    /**
119     * Adds a UIF Configurable to the trace
120     *
121     * @param configurable - The object to be added
122     */
123    public void addBean(UifDictionaryBean configurable) {
124        String beanId = "configurable";
125        String beanType = configurable.getClass().getSimpleName();
126        addBean(beanType, beanId);
127    }
128
129    /**
130     * Removes an entry from the trace
131     *
132     * @param index
133     */
134    public void removeBean(int index) {
135        beanIds.remove(index);
136        beanTypes.remove(index);
137    }
138
139    /**
140     * Replaces a trace entry's information
141     *
142     * @param index - The location of the bean
143     * @param beanId - An identifier for the bean
144     * @param beanType - The type of bean
145     */
146    public void modifyBean(int index, String beanId, String beanType) {
147        beanIds.set(index, beanId);
148        beanTypes.set(index, beanType);
149    }
150
151    /**
152     * Creates a copy of the ValidationTrace
153     *
154     * @return A complete copy of the current token
155     */
156    public ValidationTrace getCopy() {
157        ValidationTrace copy = new ValidationTrace();
158
159        for (int i = 0; i < getTraceSize(); i++) {
160            copy.addBean(getBeanType(i), getBeanId(i));
161        }
162        copy.setValidationStage(getValidationStage());
163        copy.setBeanMap(beanMap);
164        return copy;
165    }
166
167    /**
168     * Loads the xmlFiles of the data objects being validated into a list of Documents that can be parsed to find the
169     * xmls related to the error.
170     *
171     * @param beanFiles - The list of file paths used in the creation of the beans
172     * @param loader - The source that was used to load the beans
173     */
174    private void loadFiles(String[] beanFiles, ResourceLoader loader) {
175        LOG.debug("Started Loading Parser Files");
176
177        for (int i = 0; i < beanFiles.length; i++) {
178            try {
179                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
180                DocumentBuilder builder = factory.newDocumentBuilder();
181                Document document;
182                String file = beanFiles[i];//.substring(0,10)+"/"+beanFiles[i].substring(10);
183                LOG.debug("Loading file: " + file);
184                document = builder.parse(loader.getResource(file).getInputStream());
185                beanMap.put(file, document);
186            } catch (Exception e) {
187                LOG.error("Not Found: " + beanFiles[i]);
188            }
189        }
190        LOG.debug("Finished Loading Parser Files");
191    }
192
193    /**
194     * Parse the the Documents contained in the Map an finding the map entries whos documents contain the passed in Id
195     * in a bean element's attributes. All attributes are checked because the id used in the trace can be from
196     * different
197     * properties on different beans and not just the id attribute.
198     *
199     * @param id - The attribute value to be found
200     * @param beans - A Map containing the Documents to be looked through
201     * @return - A sub set of maps from the past in list that contains the value being looked for
202     */
203    private Map<String, Document> findBeanById(String id, Map<String, Document> beans) {
204        Map<String, Document> result = new HashMap<String, Document>();
205        LOG.debug("Searching for bean of Id: " + id);
206
207        Iterator iter = beans.entrySet().iterator();
208
209        while (iter.hasNext()) {
210            Map.Entry entry = (Map.Entry) iter.next();
211            Document document = (Document) entry.getValue();
212            NodeList nodes = document.getElementsByTagName("bean");
213
214            for (int i = 0; i < nodes.getLength(); i++) {
215                if (nodes.item(i).hasAttributes()) {
216                    for (int j = 0; j < nodes.item(i).getAttributes().getLength(); j++) {
217                        if (nodes.item(i).getAttributes().item(j).getNodeValue().toLowerCase().compareTo(
218                                id.toLowerCase()) == 0) {
219                            LOG.debug("Found bean of Id = " + id);
220
221                            result.put((String) entry.getKey(), (Document) entry.getValue());
222
223                            break;
224                        }
225                    }
226                }
227            }
228        }
229
230        return result;
231    }
232
233    /**
234     * Finds related xml files to an error by searching for files that contain beans that have been encountered in the
235     * validation.  The file path and Document version of the xmls are paired and stored in a Map.  This allows for
236     * returning the file paths easy when searching through the Documents.
237     *
238     * @return A list of file paths to the xmls in which the beans were found
239     */
240    public ArrayList<String> findXmlFiles() {
241        Map<String, Document> result = new HashMap<String, Document>();
242        LOG.debug("Looking for Xml files");
243
244        for (int i = 0; i < getTraceSize(); i++) {
245            if (getBeanId(i) != null) {
246                if (getBeanId(i).compareTo(NO_BEAN_ID) != 0) {
247                    result.putAll(findBeanById(getBeanId(i), beanMap));
248                }
249            }
250        }
251
252        ArrayList<String> files = new ArrayList<String>();
253        Iterator iter = result.entrySet().iterator();
254        while (iter.hasNext()) {
255            Map.Entry entry = (Map.Entry) iter.next();
256            files.add((String) entry.getKey());
257        }
258
259        return files;
260    }
261
262    /**
263     * Sets the stage of the validation where the trace is taking place
264     *
265     * @param stage - The stage of the validation
266     */
267    public void setValidationStage(int stage) {
268        validationStage = stage;
269    }
270
271    /**
272     * Sets the beanMap for when copying the tracer
273     *
274     * @param newMap - The map to be stored
275     */
276    private void setBeanMap(Map<String, Document> newMap) {
277        beanMap = newMap;
278    }
279
280    /**
281     * Creates a new error report as an Error and adds it to the global list.
282     *
283     * @param validation - The validation that fails.
284     * @param values - The values involved.
285     */
286    public void createError(String validation, String values[]) {
287        ErrorReport report = new ErrorReport(ErrorReport.ERROR, validation, this, values);
288        Validator.addErrorReport(report);
289
290    }
291
292    /**
293     * Creates a new error report as a Warning and adds it to the global list.
294     *
295     * @param validation - The validation that fails.
296     * @param values - The values involved.
297     */
298    public void createWarning(String validation, String values[]) {
299        ErrorReport report = new ErrorReport(ErrorReport.WARNING, validation, this, values);
300        Validator.addErrorReport(report);
301
302    }
303
304    /**
305     * Retrieves a single entry in the BeanId trace list, a collection identifiers for the traced beans
306     *
307     * @param index - The location of the bean
308     * @return String Identifier for the bean at the provided index of the trace
309     */
310    public String getBeanId(int index) {
311        return beanIds.get(index);
312    }
313
314    /**
315     * Retrieves a single entry in the BeanType trace list, a collection of types for the traced beansa collection
316     * identifiers for the traced beans
317     *
318     * @param index - The location of the bean type
319     * @return String Type for the bean at the provided index of the trace
320     */
321    public String getBeanType(int index) {
322        return beanTypes.get(index);
323    }
324
325    /**
326     * Retrieves the stage when the trace is taking place
327     * The stage is the time frame when the validation is taking place in the application
328     *
329     * @return Returns the stage of the validation.
330     */
331    public int getValidationStage() {
332        return validationStage;
333    }
334
335    /**
336     * Retrieves the number of beans in the trace list
337     *
338     * @return Number of beans stored in the trace
339     */
340    public int getTraceSize() {
341        return beanIds.size();
342    }
343
344    /**
345     * Retrieves the complete trace path with each bean shown in the form beanId(BeanType)
346     *
347     * @return The String path of the trace
348     */
349    public String getBeanLocation() {
350        String path = "";
351
352        for (int i = 0; i < beanTypes.size() - 1; i++) {
353            path = path + beanTypes.get(i) + "(" + beanIds.get(i) + ")" + ".";
354        }
355
356        if (getTraceSize() > 0) {
357            path = path + beanTypes.get(beanTypes.size() - 1) + "(" + beanIds.get(beanTypes.size() - 1) + ")";
358        }
359
360        return path;
361    }
362
363    /**
364     * Retrieves the list of xmls file paths found to be related to error
365     *
366     * @return A list of file paths to the related xmls
367     */
368    public ArrayList<String> getRelatedXmls() {
369        return findXmlFiles();
370    }
371}