001/*
002 * #%L
003 * Netarchivesuite - deploy
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023package dk.netarkivet.deploy;
024
025import java.io.File;
026import java.util.List;
027
028import org.dom4j.Document;
029import org.dom4j.DocumentException;
030import org.dom4j.Element;
031import org.dom4j.io.SAXReader;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import dk.netarkivet.common.exceptions.ArgumentNotValid;
036import dk.netarkivet.common.exceptions.IOFailure;
037import dk.netarkivet.common.utils.FileUtils;
038
039/**
040 * Class for evaluating a config file. Tests the settings in the config file against default settings to test for
041 * wrongly assigned elements.
042 */
043public class EvaluateConfigFile {
044
045    /** The elements to check the settings against. */
046    private Element completeSettings;
047    /** The root element in the xml tree. */
048    private XmlStructure root;
049    /** the log, for logging stuff instead of displaying them directly. */
050    private static final Logger log = LoggerFactory.getLogger(EvaluateConfigFile.class);
051
052    /**
053     * Constructor. Only initialises the config file and settings list.
054     *
055     * @param deployConfigFile The file to evaluate.
056     * @param encoding the encoding to use to read from file
057     */
058    public EvaluateConfigFile(File deployConfigFile, String encoding) {
059        ArgumentNotValid.checkNotNull(deployConfigFile, "File deployConfigFile");
060        initLoadDefaultSettings();
061        root = new XmlStructure(deployConfigFile, encoding);
062    }
063
064    /**
065     * Evaluates the config file. This is done by evaluating the settings branch for all the instances in the XML-tree
066     * (global, physical locaiton, machine and application)
067     */
068    @SuppressWarnings("unchecked")
069    public void evaluate() {
070        try {
071            // check global settings
072            evaluateElement(root.getChild(Constants.COMPLETE_SETTINGS_BRANCH));
073            List<Element> physLocs = root.getChildren(Constants.DEPLOY_PHYSICAL_LOCATION);
074            for (Element pl : physLocs) {
075                // check physical location settings
076                evaluateElement(pl.element(Constants.COMPLETE_SETTINGS_BRANCH));
077                List<Element> macs = pl.elements(Constants.DEPLOY_MACHINE);
078                for (Element mac : macs) {
079                    // check machine settings
080                    evaluateElement(mac.element(Constants.COMPLETE_SETTINGS_BRANCH));
081                    List<Element> apps = mac.elements(Constants.DEPLOY_APPLICATION_NAME);
082                    for (Element app : apps) {
083                        // check application settings
084                        evaluateElement(app.element(Constants.COMPLETE_SETTINGS_BRANCH));
085                    }
086                }
087            }
088        } catch (Exception e) {
089            log.error("Error occured during evaluation: ", e);
090        }
091    }
092
093    /**
094     * Load the default settings files as reference trees. These are used for testing whether the branches in the
095     * settings file are to be used or not.
096     */
097    private void initLoadDefaultSettings() {
098        File f = FileUtils.getResourceFileFromClassPath(Constants.BUILD_COMPLETE_SETTINGS_FILE_PATH);
099        try {
100            Document doc;
101            SAXReader reader = new SAXReader();
102            if (f.canRead()) {
103                doc = reader.read(f);
104                completeSettings = doc.getRootElement();
105            } else {
106                log.warn("Cannot read file: '{}'", f.getAbsolutePath());
107            }
108        } catch (DocumentException e) {
109            log.error("Cannot handle complete settings file.", e);
110            throw new IOFailure("Cannot handle complete settings file.", e);
111        }
112    }
113
114    /**
115     * Evaluates a element (has to called with the settings branch). Then tries to evaluate all the branches to the
116     * element. The method is called recursively for the children of curElem.
117     *
118     * @param curElem The current element to evaluate. Null element represents in this context that no settings branch
119     * exists for the current instance.
120     */
121    @SuppressWarnings("unchecked")
122    private void evaluateElement(Element curElem) {
123        // make sure to catch null-pointers
124        if (curElem == null) {
125            return;
126        }
127        List<Element> elList = curElem.elements();
128        for (Element el : elList) {
129            boolean valid = false;
130            // get path
131            String path = getSettingsPath(el);
132
133            // check if path exists in any default setting.
134            valid = existBranch(completeSettings, path.split(Constants.SLASH));
135
136            if (valid) {
137                if (!el.isTextOnly()) {
138                    evaluateElement(el);
139                }
140            } else {
141                // Print out the 'illegal' branches.
142                System.out.println("Branch in settings not found: " + path.replace(Constants.SLASH, Constants.DOT));
143            }
144        }
145    }
146
147    /**
148     * For testing whether a branch with the current path exists.
149     *
150     * @param settings The root of the default settings XML-tree.
151     * @param path The path to the branch to test.
152     * @return Whether the branch at the end of the path in the root exists.
153     */
154    private boolean existBranch(Element settings, String[] path) {
155        Element curE = settings;
156        for (String st : path) {
157            if (curE == null) {
158                return false;
159            }
160            curE = curE.element(st);
161        }
162
163        // return whether the final branch exists.
164        return (curE != null);
165    }
166
167    /**
168     * Gets the path from settings of an element.
169     *
170     * @param el The element to get the settings path.
171     * @return The path from settings to the element, in the XML-tree.
172     */
173    private String getSettingsPath(Element el) {
174        String[] elList = el.getPath().split(Constants.SLASH);
175
176        StringBuilder res = new StringBuilder();
177        int i = 0;
178        // find the index for settings
179        while (i < elList.length && !elList[i].equalsIgnoreCase(Constants.COMPLETE_SETTINGS_BRANCH)) {
180            ++i;
181        }
182
183        // TODO WTF?!
184        for (i++; i < elList.length; i++) {
185            res.append(elList[i]);
186            res.append(Constants.SLASH);
187        }
188
189        // remove last '/'
190        res.deleteCharAt(res.length() - 1);
191
192        return res.toString();
193    }
194
195}