1 /* 
2  * Copyright 2005 Paul Hinds
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.tp23.antinstaller.runtime.exe;
17
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.IOException;
21import java.io.InputStream;
22import java.lang.reflect.InvocationTargetException;
23import java.lang.reflect.Method;
24import java.util.ArrayList;
25
26import javax.xml.parsers.DocumentBuilder;
27import javax.xml.parsers.DocumentBuilderFactory;
28
29import org.tp23.antinstaller.InstallException;
30import org.tp23.antinstaller.Installer;
31import org.tp23.antinstaller.InstallerContext;
32import org.tp23.antinstaller.input.AppRootInput;
33import org.tp23.antinstaller.input.CheckboxInput;
34import org.tp23.antinstaller.input.CommentOutput;
35import org.tp23.antinstaller.input.ConditionalField;
36import org.tp23.antinstaller.input.ConfirmPasswordTextInput;
37import org.tp23.antinstaller.input.DateInput;
38import org.tp23.antinstaller.input.DirectoryInput;
39import org.tp23.antinstaller.input.ExtValidatedTextInput;
40import org.tp23.antinstaller.input.FileInput;
41import org.tp23.antinstaller.input.HiddenPropertyInput;
42import org.tp23.antinstaller.input.InputField;
43import org.tp23.antinstaller.input.LargeSelectInput;
44import org.tp23.antinstaller.input.OutputField;
45import org.tp23.antinstaller.input.PasswordTextInput;
46import org.tp23.antinstaller.input.SelectInput;
47import org.tp23.antinstaller.input.TargetInput;
48import org.tp23.antinstaller.input.TargetSelectInput;
49import org.tp23.antinstaller.input.UnvalidatedTextInput;
50import org.tp23.antinstaller.input.ValidatedTextInput;
51import org.tp23.antinstaller.page.LicensePage;
52import org.tp23.antinstaller.page.Page;
53import org.tp23.antinstaller.page.ProgressPage;
54import org.tp23.antinstaller.page.SimpleInputPage;
55import org.tp23.antinstaller.page.SplashPage;
56import org.tp23.antinstaller.page.TextPage;
57import org.tp23.antinstaller.runtime.ConfigurationException;
58import org.w3c.dom.Document;
59import org.w3c.dom.Element;
60import org.w3c.dom.NamedNodeMap;
61import org.w3c.dom.Node;
62import org.w3c.dom.NodeList;
63import org.xml.sax.EntityResolver;
64import org.xml.sax.InputSource;
65
66
67/**
68 * Loads the Ant Install configuration and sets the Installer object back
69 * into the context.  A similar technique to the apache digester is used to
70 * populate object attributes in this class.
71 * N.B. The only types that can be set for object attributes are Strings, booleans
72 * or ints.
73 * @author Paul Hinds
74 * @version $Id: LoadConfigFilter.java,v 1.9 2007/01/28 08:44:39 teknopaul Exp $
75 */
76public class LoadConfigFilter implements ExecuteFilter {
77
78    //TODO move to InstallerContext
79    public static final String INSTALLER_CONFIG_FILE = "antinstall-config.xml";
80    
81    protected Installer installer = new Installer();
82    protected InstallerContext ctx;
83
84    /**
85     * @see org.tp23.antinstaller.runtime.exe.ExecuteFilter#exec(org.tp23.antinstaller.InstallerContext)
86     */
87    public void exec(InstallerContext ctx) throws InstallException {
88        this.ctx = ctx;
89        
90        try {
91            installer = readConfig(ctx.getFileRoot(), ctx.getInstallerConfigFile());
92            ctx.setInstaller(installer);
93            ctx.log("Config loaded");
94        }
95        catch (IOException e) {
96            throw new InstallException("Not able to load and read the AntInstaller config", e);
97        }
98        catch (ConfigurationException e) {
99            throw new InstallException("Not able to load and read the AntInstaller config", e);
00        }
01    }
02
03    /**
04     * Currently read the config using any available XML parser
05     * This method reads the config from the file system
06     * @param fileRoot The directory where the config file is stored
07     * @param the name of the configuration file (usually antinstall-config.xml)
08     * @return Installer
09     */
10    public Installer readConfig(File fileRoot, String fileName) throws IOException, ConfigurationException {
11
12        installer.getResultContainer().setInstallRoot(fileRoot);
13
14        File config = new File(fileRoot, fileName);
15        if(!config.exists()){ // passed in incorrectly on the command line or bad installer
16            throw new IOException();
17        }
18        InputSource xmlInp = new InputSource(new FileInputStream(config));
19        readConfig(xmlInp);
20        
21        return installer;
22    }
23    /**
24     * This overloaded method reads from the provided input stream to facilitate
25     * reading configs directly from the Jar, the file root is still needed 
26     * for Ant's basedir. Used by InputStreamLoadConfigFilter
27     * @return Installer
28     */
29    protected Installer readConfig(File fileRoot, InputStream configSource) throws IOException, ConfigurationException {
30
31        installer.getResultContainer().setInstallRoot(fileRoot);
32
33        InputSource xmlInp = new InputSource(configSource);
34        readConfig(xmlInp);
35        
36        return installer;
37    }
38    /**
39     * Currently read the config using any available XML parser
40     * @todo read the installer with only xerces
41     * @return Installer
42     */
43    protected Installer readConfig(InputSource xmlInp) throws IOException, ConfigurationException {
44
45        Document doc = null;
46        try {
47
48            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
49            DocumentBuilder parser = docBuilderFactory.newDocumentBuilder();
50            
51            // RFE [ 1475361 ] Using a custom entity resolver
52            String entityResolverClass = System.getProperty("antinstaller.EntityResolverClass");
53            EntityResolver er = null;
54            if(entityResolverClass != null){
55                try{
56                    er = (EntityResolver)Class.forName(entityResolverClass).newInstance();
57                }
58                catch(Exception e){
59                    // Property error use default this is a very specific requirement
60                    er = new CustomEntityResolver();
61                }
62            }
63            else{
64                er = new CustomEntityResolver();
65            }
66            
67            parser.setEntityResolver(er);
68
69            doc = parser.parse(xmlInp);
70            Element root = doc.getDocumentElement();
71            root.normalize();
72            setProperties(installer, root.getAttributes());
73            NodeList allPages = root.getElementsByTagName("page");
74            //TODO make this pluggable
75            getPages(installer, allPages);
76
77        }
78        catch (Exception e) {
79            throw new IOException("DomFactory error: caused by:" + e.getClass() + ":" + e.getMessage());
80        }
81        return installer;
82    }
83
84
85    /**
86     * Used when reading the config
87     * @param allPages NodeList
88     * @throws ConfigurationException
89     */
90    private void getPages(Installer installerConfig, NodeList allPages) throws ConfigurationException {
91        ArrayList pages = new ArrayList();
92        for (int i = 0; i < allPages.getLength(); i++) {
93            Element pageElem = (Element) allPages.item(i);
94            Page page = getPageType(pageElem.getAttribute("type"));
95            setProperties(page, pageElem.getAttributes());
96            pages.add(page);
97            getOutputFields(page, pageElem);
98        }
99        Page[] pageArr = new Page[pages.size()];
00        pages.toArray(pageArr);
01        installerConfig.setPages(pageArr);
02    }
03    /**
04     * Used when reading the config
05     * @param page Page
06     * @param pageElem Element
07     * @throws ConfigurationException
08     */
09    private void getOutputFields(Page page, Element pageElem) throws ConfigurationException {
10        page.setOutputField( getInnerOutputFields( pageElem ));
11    }
12
13    private OutputField[] getInnerOutputFields( Element elem) throws ConfigurationException {
14         NodeList allFields = elem.getChildNodes();
15         ArrayList fields = new ArrayList();
16         for (int i = 0; i < allFields.getLength(); i++) {
17             if(! (allFields.item(i) instanceof Element))continue;
18             Element fieldElem = (Element)allFields.item(i);
19             OutputField field = getOutputFieldType(fieldElem.getNodeName(), fieldElem);
20             if(field != null){
21                 setProperties(field, fieldElem.getAttributes());
22                 fields.add(field);
23                 field.setResultContainer(installer.getResultContainer());
24             }
25         }
26         OutputField[] fieldArr = new OutputField[fields.size()];
27
28         return (OutputField[])fields.toArray(fieldArr);
29     }
30
31    /**
32     * Used when reading the config
33     * @param type String
34     * @throws ConfigurationException
35     * @return Page
36     */
37    private Page getPageType(String type) throws ConfigurationException {
38        if (type.equalsIgnoreCase("license")) {
39            return new LicensePage();
40        }
41        else if (type.equalsIgnoreCase("input")) {
42            return new SimpleInputPage();
43        }
44        else if (type.equalsIgnoreCase("progress")) {
45            return new ProgressPage();
46        }
47        else if (type.equalsIgnoreCase("splash")) {
48            return new SplashPage();
49        }
50        else if (type.equalsIgnoreCase("text")) {
51            return new TextPage();
52        }
53        throw new ConfigurationException("Unknown Page type:" + type);
54    }
55    /**
56     * Used when reading the config
57     * @param type String
58     * @param field Element
59     * @throws ConfigurationException
60     * @return InputField
61     */
62    private OutputField getOutputFieldType(String type, Element field) throws ConfigurationException {
63        if (type.equalsIgnoreCase("text")) {
64            return new UnvalidatedTextInput();
65        }
66        else if (type.equalsIgnoreCase("directory")) {
67            return new DirectoryInput();
68        }
69        else if (type.equalsIgnoreCase("target")) {
70            return new TargetInput();
71        }
72        else if (type.equalsIgnoreCase("file")) {
73            return new FileInput();
74        }
75        else if (type.equalsIgnoreCase("comment")) {
76            return new CommentOutput();
77        }
78        else if (type.equalsIgnoreCase("checkbox")) {
79            return new CheckboxInput();
80        }
81        else if (type.equalsIgnoreCase("validated")) {
82            return new ValidatedTextInput();
83        }
84        else if (type.equalsIgnoreCase("ext-validated")) {
85            return new ExtValidatedTextInput();
86        }
87        else if (type.equalsIgnoreCase("password")) {
88            return new PasswordTextInput();
89        }
90        else if (type.equalsIgnoreCase("password-confirm")) {
91            return new ConfirmPasswordTextInput();
92        }
93        else if (type.equalsIgnoreCase("hidden")) {
94             return new HiddenPropertyInput();
95         }
96        else if (type.equalsIgnoreCase("date")) {
97            return new DateInput();
98        }
99        else if (type.equalsIgnoreCase("app-root")) {
00            return new AppRootInput();
01        }
02        else if (type.equalsIgnoreCase("conditional")) {
03            ConditionalField conditionalField = new ConditionalField();
04            OutputField[] outFields = getInnerOutputFields( field );
05            InputField[] inFields = new InputField[ outFields.length ];
06            for( int i = 0; i < outFields.length; i++ ) {
07                inFields[i] = (InputField) outFields[i];
08            }
09            conditionalField.setFields( inFields );
10            return conditionalField;
11        }
12        else if (type.equalsIgnoreCase("select")) {
13            SelectInput sInput = new SelectInput();
14            NodeList allOptions = field.getElementsByTagName("option");
15            ArrayList options = new ArrayList();
16            for (int i = 0; i < allOptions.getLength(); i++) {
17                Element optionElem = (Element) allOptions.item(i);
18                SelectInput.Option option = sInput.getNewOption();
19                option.setText(optionElem.getAttribute("text"));
20                option.value = optionElem.getAttribute("value");
21                options.add(option);
22            }
23            SelectInput.Option[] optionArr = new SelectInput.Option[options.size()];
24            options.toArray(optionArr);
25            sInput.setOptions(optionArr);
26
27            return sInput;
28        }
29        else if (type.equalsIgnoreCase("target-select")) {
30            TargetSelectInput sInput = new TargetSelectInput();
31            NodeList allOptions = field.getElementsByTagName("option");
32            ArrayList options = new ArrayList();
33            for (int i = 0; i < allOptions.getLength(); i++) {
34                Element optionElem = (Element) allOptions.item(i);
35                SelectInput.Option option = sInput.getNewOption();
36                option.setText(optionElem.getAttribute("text"));
37                option.value = optionElem.getAttribute("value");
38                options.add(option);
39            }
40            SelectInput.Option[] optionArr = new SelectInput.Option[options.size()];
41            options.toArray(optionArr);
42            sInput.setOptions(optionArr);
43
44            return sInput;
45        }
46        else if (type.equalsIgnoreCase("large-select")) {
47            LargeSelectInput sInput = new LargeSelectInput();
48            NodeList allOptions = field.getElementsByTagName("option");
49            ArrayList options = new ArrayList();
50            for (int i = 0; i < allOptions.getLength(); i++) {
51                Element optionElem = (Element) allOptions.item(i);
52                LargeSelectInput.Option option = sInput.getNewOption();
53                option.setText(optionElem.getAttribute("text"));
54                option.value = optionElem.getAttribute("value");
55                options.add(option);
56            }
57            LargeSelectInput.Option[] optionArr = new LargeSelectInput.Option[options.size()];
58            options.toArray(optionArr);
59            sInput.setOptions(optionArr);
60
61            return sInput;
62        }
63        System.out.println("Unrecognised Input Element:"+type);
64        return null;
65        //throw new ConfigurationException("Unknown Input Field type:" + type);
66    }
67
68
69
70    /**
71     * Calls bean setter methods based on attribures found. Could use BeanUtils here
72     * but we want to stay clear of external dependencies.
73     * @param bean Object
74     * @param map NamedNodeMap
75     */
76    private void setProperties(Object bean, NamedNodeMap map) {
77        int numAtts = map.getLength();
78        for (int a = 0; a < numAtts; a++) {
79            Node attribute = map.item(a);
80            String name = attribute.getNodeName();
81            String value = attribute.getNodeValue();
82            String methodName = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
83            Method[] allMethods = bean.getClass().getMethods();
84            for (int m = 0; m < allMethods.length; m++) {
85                if (allMethods[m].getName().equals(methodName)) {
86                    try {
87                        Class[] parameters = allMethods[m].getParameterTypes();
88                        Object[] paramValues;
89                        if (parameters[0].equals(Boolean.class)) {
90                            paramValues = new Boolean[1];
91                            if ( OutputField.isTrue(value) ) {
92                                paramValues[0] = Boolean.TRUE;
93                            }
94                            else {
95                                paramValues[0] = Boolean.FALSE;
96                            }
97                        }
98                        else
99                        if (parameters[0].equals(Integer.class)) {
00                            paramValues = new Integer[1];
01                            paramValues[0] = new Integer(value);
02                        }
03                        else {
04                            paramValues = new String[1];
05                            paramValues[0] = value;
06                        }
07                        allMethods[m].invoke(bean, paramValues);
08                        continue;
09                    }
10                    catch (IndexOutOfBoundsException ex) {
11                        // not the setter we are looking for
12                        // this is the wrong overloaded method
13                        continue; 
14                    }
15                    // Ignore reflection errors and continue
16                    catch (IllegalArgumentException e) {
17                    }
18                    catch (IllegalAccessException e) {                  
19                    }
20                    catch (InvocationTargetException e) {                       
21                    }
22                }
23            }
24        }
25    }
26
27    private static class CustomEntityResolver implements EntityResolver{
28        public InputSource resolveEntity(String publicId, String systemId) {
29            
30            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
31                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.2.dtd")) {
32                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
33                    "/org/tp23/antinstaller/antinstall-config-0.2.dtd"));
34                return localSrc;
35            }
36            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
37                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.3.dtd")) {
38                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
39                    "/org/tp23/antinstaller/antinstall-config-0.3.dtd"));
40                return localSrc;
41            }
42            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
43                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.4.dtd")) {
44                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
45                    "/org/tp23/antinstaller/antinstall-config-0.4.dtd"));
46                return localSrc;
47            }
48            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
49                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.5.dtd")) {
50                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
51                    "/org/tp23/antinstaller/antinstall-config-0.5.dtd"));
52                return localSrc;
53            }
54            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
55                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.6.dtd")) {
56                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
57                    "/org/tp23/antinstaller/antinstall-config-0.6.dtd"));
58                return localSrc;
59            }
60            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
61                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.7.dtd")) {
62                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
63                    "/org/tp23/antinstaller/antinstall-config-0.7.dtd"));
64                return localSrc;
65            }
66            if (publicId.equals("-//tp23 //DTD Ant Installer Config//EN") &&
67                systemId.equals("http://antinstaller.sf.net/dtd/antinstall-config-0.8.dtd")) {
68                InputSource localSrc = new InputSource(this.getClass().getResourceAsStream(
69                    "/org/tp23/antinstaller/antinstall-config-0.8.dtd"));
70                return localSrc;
71            }
72            else {
73                return null;
74            }
75        }
76    }
77}
78