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.io.FileNotFoundException;
027import java.io.PrintWriter;
028import java.io.UnsupportedEncodingException;
029import java.util.List;
030
031import org.dom4j.Attribute;
032import org.dom4j.Element;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import dk.netarkivet.common.exceptions.ArgumentNotValid;
037import dk.netarkivet.common.exceptions.IOFailure;
038import dk.netarkivet.common.exceptions.IllegalState;
039
040/**
041 * The application entity in the deploy hierarchy.
042 */
043public class Application {
044
045    /** the log, for logging stuff instead of displaying them directly. */
046    private static final Logger log = LoggerFactory.getLogger(Application.class);
047
048    /** the root-branch for this application in the XML tree. */
049    private Element applicationRoot;
050    /** The specific settings for this instance, inherited and overwritten. */
051    private XmlStructure settings;
052    /** parameters. */
053    private Parameters machineParameters;
054    /** Name of this instance. */
055    private String name;
056    /** The total name of this instance. */
057    private String nameWithNamePath;
058    /** application instance id (optional, used when two application has same name). */
059    private String applicationInstanceId;
060
061    /** The encoding to use when writing files. */
062    private final String targetEncoding;
063
064    /**
065     * A application is the program to be run on a machine.
066     *
067     * @param subTreeRoot The root of this instance in the XML document.
068     * @param parentSettings The setting inherited by the parent.
069     * @param param The machine parameters inherited by the parent.
070     * @param targetEncoding the encoding to use when writing files.
071     */
072    public Application(Element subTreeRoot, XmlStructure parentSettings, Parameters param, String targetEncoding) {
073        ArgumentNotValid.checkNotNull(subTreeRoot, "Element e");
074        ArgumentNotValid.checkNotNull(parentSettings, "XmlStructure parentSettings");
075        ArgumentNotValid.checkNotNull(param, "Parameters param");
076        ArgumentNotValid.checkNotNullOrEmpty(targetEncoding, "targetEncoding");
077        this.targetEncoding = targetEncoding;
078        settings = new XmlStructure(parentSettings.getRoot());
079        applicationRoot = subTreeRoot;
080        machineParameters = new Parameters(param);
081        // retrieve the specific settings for this instance
082        Element tmpSet = applicationRoot.element(Constants.COMPLETE_SETTINGS_BRANCH);
083        // Generate the specific settings by combining the general settings
084        // and the specific, (only if this instance has specific settings)
085        if (tmpSet != null) {
086            settings.overWrite(tmpSet);
087        }
088        // check if new machine parameters
089        machineParameters.newParameters(applicationRoot);
090        // Retrieve the variables for this instance.
091        extractVariables();
092    }
093
094    /**
095     * Extract the local variables from the root.
096     * <p>
097     * Currently, this is the name and the optional applicationId.
098     */
099    private void extractVariables() {
100        try {
101            // retrieve name
102            Attribute at = applicationRoot.attribute(Constants.APPLICATION_NAME_ATTRIBUTE);
103            if (at != null) {
104                // the name is actually the classpath, so the specific class is
105                // set as the name. It is the last element in the classpath.
106                nameWithNamePath = at.getText().trim();
107                // the classpath is is separated by '.'
108                String[] stlist = nameWithNamePath.split(Constants.REGEX_DOT_CHARACTER);
109                // take the last part of the application class path as name.
110                // e.g.
111                // dk.netarkivet.archive.bitarhcive.BitarchiveMonitorApplication
112                // gets the name BitarchiveMonitorApplication.
113                name = stlist[stlist.length - 1];
114
115                // overwriting the name, if it exists already;
116                // otherwise it is inserted.
117                String xmlName = XmlStructure.pathAndContentToXML(nameWithNamePath,
118                        Constants.COMPLETE_APPLICATION_NAME_LEAF);
119                Element appXmlName = XmlStructure.makeElementFromString(xmlName);
120                settings.overWrite(appXmlName);
121            } else {
122                log.warn("Application has no name!");
123                throw new IllegalState("Application has no name!");
124            }
125            // look for the optional application instance id
126            Element elem = settings.getSubChild(Constants.SETTINGS_APPLICATION_INSTANCE_ID_LEAF);
127            if (elem != null && !elem.getText().trim().isEmpty()) {
128                applicationInstanceId = elem.getText().trim();
129            }
130        } catch (Exception e) {
131            log.debug("Application variables not extractable.", e);
132            throw new IOFailure("Application variables not extractable.", e);
133        }
134    }
135
136    /**
137     * Uses the name and the optional applicationId to create an unique identification for this application.
138     *
139     * @return The unique identification of this application.
140     */
141    public String getIdentification() {
142        StringBuilder res = new StringBuilder(name);
143        // use only applicationInstanceId if it exists and has content
144        if (applicationInstanceId != null && !applicationInstanceId.isEmpty()) {
145            res.append(Constants.UNDERSCORE);
146            res.append(applicationInstanceId);
147        }
148        return res.toString();
149    }
150
151    /**
152     * @return the total name with directory path.
153     */
154    public String getTotalName() {
155        return nameWithNamePath;
156    }
157
158    /**
159     * Creates the settings file for this application. This is extracted from the XMLStructure and put into a specific
160     * file. The name of the settings file for this application is: "settings_" + identification + ".xml".
161     *
162     * @param directory The directory where the settings file should be placed.
163     */
164    public void createSettingsFile(File directory) {
165        ArgumentNotValid.checkNotNull(directory, "File directory");
166
167        // make file
168        File settingsFile = new File(directory, Constants.PREFIX_SETTINGS + getIdentification()
169                + Constants.EXTENSION_XML_FILES);
170        try {
171            // initiate writer
172            PrintWriter pw = new PrintWriter(settingsFile, targetEncoding);
173            try {
174                // Extract the XML content of the branch for this application
175                pw.println(settings.getXML());
176            } finally {
177                pw.close();
178            }
179        } catch (FileNotFoundException e) {
180            log.debug("Cannot create settings file for an application.", e);
181            throw new IOFailure("Cannot create settings file for an application.", e);
182        } catch (UnsupportedEncodingException e) {
183            log.debug("Unsupported encoding '{}'", targetEncoding, e);
184            throw new IOFailure("Unsupported encoding '" + targetEncoding + "'", e);
185        }
186    }
187
188    /**
189     * Makes the install path with linux syntax.
190     *
191     * @return The path in linux syntax.
192     */
193    public String installPathLinux() {
194        return machineParameters.getInstallDirValue() + Constants.SLASH
195                + settings.getSubChildValue(Constants.SETTINGS_ENVIRONMENT_NAME_LEAF);
196    }
197
198    /**
199     * Makes the install path with windows syntax.
200     *
201     * @return The path with windows syntax.
202     */
203    public String installPathWindows() {
204        return machineParameters.getInstallDirValue() + Constants.BACKSLASH
205                + settings.getSubChildValue(Constants.SETTINGS_ENVIRONMENT_NAME_LEAF);
206    }
207
208    /**
209     * For acquiring the machine parameter variable.
210     *
211     * @return The machine parameter variable.
212     */
213    public Parameters getMachineParameters() {
214        return machineParameters;
215    }
216
217    /**
218     * For acquiring all the values of the leafs at the end of the path.
219     *
220     * @param path The path to the branches.
221     * @return The values of the leafs. If no values were found, then an empty collection of strings are returned.
222     */
223    public String[] getSettingsValues(String[] path) {
224        ArgumentNotValid.checkNotNull(path, "String[] path");
225        ArgumentNotValid.checkNotNegative(path.length, "Length of String[] path");
226        return settings.getLeafValues(path);
227    }
228
229    /**
230     * Returns the settings XML subtree for the application.
231     * @return the settings XML subtree for the application
232     */
233    public XmlStructure getSettings() {
234        return settings;
235    }
236
237    /**
238     * Detects whether this is a Harvester app, which requires a harvester bundle to be deployed.
239     * @return <code>true if the is a harvester requiring a harvester bundle, else <code>false</code>.</code>
240     */
241    public boolean isBundledHarvester() {
242        List<Element> classPaths = getMachineParameters().getClassPaths();
243        for (Element classPathElement : classPaths) {
244            if (classPathElement.getText().contains("heritrix3")) {
245                return true;
246            }
247        }
248        return false;
249    }
250
251}