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.IOException;
027import java.io.PrintWriter;
028import java.util.ArrayList;
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 physical location class.
042 */
043public class PhysicalLocation {
044
045    /** the log, for logging stuff instead of displaying them directly. */
046    private static final Logger log = LoggerFactory.getLogger(PhysicalLocation.class);
047    /** The root for the branch of this element in the XML-tree. */
048    private Element physLocRoot;
049    /** The settings structure. */
050    private XmlStructure settings;
051    /** The parameters for java. */
052    private Parameters machineParameters;
053    /** The list of the machines. */
054    private List<Machine> machines;
055    /** The name of this physical location. */
056    private String name;
057    /** The inherited name for the NetarchiveSuite file. */
058    private String netarchiveSuiteFileName;
059    /** The inherited SLF4J config file. */
060    private File slf4JConfigFile;
061    /** The inherited security file. */
062    private File securityPolicyFile;
063    /** The inherited database file name. */
064    private File databaseFile;
065    /** The inherited archive database file name. */
066    private File arcDatabaseFile;
067    /** The optional choice for resetting tempDir. */
068    private boolean resetDirectory;
069    /** The folder for the external jar libraries. */
070    private File jarFolder;
071    private final DeployConfiguration deployConfiguration;
072
073    /**
074     * The physical locations is referring to the position in the real world where the computers are located. One
075     * physical location can contain many machines.
076     *
077     * @param subTreeRoot The root of this branch in the XML structure.
078     * @param parentSettings The settings of the parent (deploy-config).
079     * @param param The parameters of the parent (deploy-config).
080     * @param netarchiveSuiteSource The name of the NetarchiveSuite file.
081     * @param securityPolicy The security policy file.
082     * @param dbFile The harvest definition database.
083     * @param arcdbFile The archive database.
084     * @param resetDir Whether the temporary directory should be reset.
085     * @param externalJarFolder The folder containing the external jar library files.
086     * @throws ArgumentNotValid If one of the following arguments are null: subTreeRoot, parentSettings, param, logProp,
087     * securityPolicy; or if the netarchiveSuiteSource if either null or empty.
088     */
089    public PhysicalLocation(Element subTreeRoot, XmlStructure parentSettings, Parameters param,
090            String netarchiveSuiteSource, File slf4JConfig, File securityPolicy, File dbFile,
091            File arcdbFile, boolean resetDir, File externalJarFolder, DeployConfiguration deployConfiguration) throws ArgumentNotValid {
092        // test if valid arguments
093        ArgumentNotValid.checkNotNull(subTreeRoot, "Element elem (physLocRoot)");
094        ArgumentNotValid.checkNotNull(parentSettings, "XmlStructure parentSettings");
095        ArgumentNotValid.checkNotNull(param, "Parameters param");
096        ArgumentNotValid.checkNotNullOrEmpty(netarchiveSuiteSource, "String netarchiveSuite");
097        // ArgumentNotValid.checkNotNull(slf4JConfig, "File slf4JConfig");
098        ArgumentNotValid.checkNotNull(securityPolicy, "File securityPolicy");
099
100        // make a copy of parent, don't use it directly.
101        settings = new XmlStructure(parentSettings.getRoot());
102        physLocRoot = subTreeRoot;
103        machineParameters = new Parameters(param);
104        netarchiveSuiteFileName = netarchiveSuiteSource;
105        slf4JConfigFile = slf4JConfig;
106        securityPolicyFile = securityPolicy;
107        databaseFile = dbFile;
108        arcDatabaseFile = arcdbFile;
109        resetDirectory = resetDir;
110        jarFolder = externalJarFolder;
111        this.deployConfiguration = deployConfiguration;
112
113        // retrieve the specific settings for this instance
114        Element tmpSet = physLocRoot.element(Constants.COMPLETE_SETTINGS_BRANCH);
115        // Generate the specific settings by combining the general settings
116        // and the specific, (only if this instance has specific settings)
117        if (tmpSet != null) {
118            settings.overWrite(tmpSet);
119        }
120        // check if new machine parameters
121        machineParameters.newParameters(physLocRoot);
122        // Retrieve the variables for this instance.
123        extractVariables();
124        // generate the machines on this instance
125        extractMachines();
126    }
127
128    /**
129     * Extract the local variables from the root.
130     * <p>
131     * It is only the name for this instance. This is then set in settings.
132     */
133    private void extractVariables() {
134        // retrieve name
135        Attribute at = physLocRoot.attribute(Constants.PHYSICAL_LOCATION_NAME_ATTRIBUTE);
136        if (at != null) {
137            name = at.getText().trim();
138            // insert the name in settings.
139            String xmlName = XmlStructure.pathAndContentToXML(name, Constants.COMPLETE_THIS_PHYSICAL_LOCATION_LEAF);
140            Element physLocName = XmlStructure.makeElementFromString(xmlName);
141            settings.overWrite(physLocName);
142        } else {
143            throw new IllegalState(Constants.MSG_ERROR_PHYSICAL_LOCATION_NO_NAME);
144        }
145    }
146
147    /**
148     * Extracts the XML for machines from the root, creates the machines, and puts them into the list.
149     */
150    @SuppressWarnings("unchecked")
151    private void extractMachines() {
152        machines = new ArrayList<Machine>();
153        List<Element> le = physLocRoot.elements(Constants.DEPLOY_MACHINE);
154        for (Element e : le) {
155            String os = getTrimmedAttributeValue(e, Constants.MACHINE_OPERATING_SYSTEM_ATTRIBUTE);
156            // only a windows machine, if the 'os' attribute exists and
157            // equals (not case-sensitive) 'windows'. Else linux machine
158            if (os != null && os.equalsIgnoreCase(Constants.OPERATING_SYSTEM_WINDOWS_ATTRIBUTE)) {
159                machines.add(new WindowsMachine(e, settings, machineParameters, netarchiveSuiteFileName,
160                        slf4JConfigFile, securityPolicyFile, databaseFile, arcDatabaseFile, resetDirectory, jarFolder));
161            } else {
162                machines.add(new LinuxMachine(e, settings, machineParameters, netarchiveSuiteFileName,
163                        slf4JConfigFile, securityPolicyFile, databaseFile, arcDatabaseFile, resetDirectory, jarFolder,
164                        deployConfiguration));
165            }
166        }
167    }
168
169    private String getTrimmedAttributeValue(Element e, String attributeName) {
170        String value = e.attributeValue(attributeName);
171        return (value != null ? value.trim() : null);
172    }
173
174    /**
175     * Initiate the creation of global scripts and machine scripts.
176     *
177     * @param directory The directory where the files are to be placed.
178     * @throws ArgumentNotValid If the directory is null.
179     */
180    public void write(File directory) throws ArgumentNotValid {
181        ArgumentNotValid.checkNotNull(directory, "File directory");
182        // make the script in the directory!
183        makeScripts(directory);
184        // write all machine at this location
185        for (Machine mac : machines) {
186            mac.write(directory);
187        }
188    }
189
190    /**
191     * Creates the following scripts for this physical location. * killall. * install. * startall.
192     * <p>
193     * The scripts for a physical location will only work from Linux/Unix.
194     *
195     * @param directory The directory where the scripts are to be placed.
196     * @throws ArgumentNotValid If the directory is null.
197     * @throws IOFailure If an error occurs during the creation of the scripts.
198     */
199    private void makeScripts(File directory) throws ArgumentNotValid, IOFailure {
200        ArgumentNotValid.checkNotNull(directory, "File directory");
201        // make extension (e.g. '_kb.sh' in the script 'killall_kb.sh')
202        String ext = Constants.UNDERSCORE + name + Constants.SCRIPT_EXTENSION_LINUX;
203        // make script files
204        File killall = new File(directory, Constants.SCRIPT_NAME_KILL_ALL + ext);
205        File install = new File(directory, Constants.SCRIPT_NAME_INSTALL_ALL + ext);
206        File startall = new File(directory, Constants.SCRIPT_NAME_START_ALL + ext);
207        try {
208            // Make the killall script for the physical location
209            PrintWriter kWriter = new PrintWriter(killall);
210            try {
211                kWriter.println(ScriptConstants.BIN_BASH_COMMENT);
212                // insert machine data
213                for (Machine mac : machines) {
214                    // write the call to the kill script of each machines
215                    kWriter.println(ScriptConstants.writeDashLine());
216                    kWriter.print(mac.writeToGlobalKillScript());
217                }
218                // write an extra line of dashes.
219                kWriter.println(ScriptConstants.writeDashLine());
220            } finally {
221                // close writer
222                kWriter.flush();
223                kWriter.close();
224            }
225
226            // Make the install script for the physical location
227            PrintWriter iWriter = new PrintWriter(install);
228            try {
229                iWriter.println(ScriptConstants.BIN_BASH_COMMENT);
230                // insert machine data
231                for (Machine mac : machines) {
232                    // write install script from machines
233                    iWriter.println(ScriptConstants.writeDashLine());
234                    iWriter.print(mac.writeToGlobalInstallScript());
235                }
236                // write an extra line of dashes.
237                iWriter.println(ScriptConstants.writeDashLine());
238            } finally {
239                // close writer
240                iWriter.flush();
241                iWriter.close();
242            }
243
244            // Make the startall script for the physical location
245            PrintWriter sWriter = new PrintWriter(startall);
246            try {
247                sWriter.println(ScriptConstants.BIN_BASH_COMMENT);
248                // insert machine data
249                for (Machine mac : machines) {
250                    // write start script from machines
251                    sWriter.println(ScriptConstants.writeDashLine());
252                    sWriter.print(mac.writeToGlobalStartScript());
253                }
254                sWriter.println(ScriptConstants.writeDashLine());
255            } finally {
256                // close writer
257                sWriter.flush();
258                sWriter.close();
259            }
260        } catch (IOException e) {
261            String msg = "Cannot create the scripts for the physical " + "location: '" + name + "'.";
262            log.trace(msg, e);
263            throw new IOFailure(msg, e);
264        }
265    }
266
267}