001: /*
002: * Copyright 2005 Paul Hinds
003: *
004: * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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: */
016: package org.tp23.antinstaller.runtime.exe;
017:
018: import java.io.File;
019: import java.io.FileInputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.util.ArrayList;
025:
026: import javax.xml.parsers.DocumentBuilder;
027: import javax.xml.parsers.DocumentBuilderFactory;
028:
029: import org.tp23.antinstaller.InstallException;
030: import org.tp23.antinstaller.Installer;
031: import org.tp23.antinstaller.InstallerContext;
032: import org.tp23.antinstaller.input.AppRootInput;
033: import org.tp23.antinstaller.input.CheckboxInput;
034: import org.tp23.antinstaller.input.CommentOutput;
035: import org.tp23.antinstaller.input.ConditionalField;
036: import org.tp23.antinstaller.input.ConfirmPasswordTextInput;
037: import org.tp23.antinstaller.input.DateInput;
038: import org.tp23.antinstaller.input.DirectoryInput;
039: import org.tp23.antinstaller.input.ExtValidatedTextInput;
040: import org.tp23.antinstaller.input.FileInput;
041: import org.tp23.antinstaller.input.HiddenPropertyInput;
042: import org.tp23.antinstaller.input.InputField;
043: import org.tp23.antinstaller.input.LangSelectInput;
044: import org.tp23.antinstaller.input.LargeSelectInput;
045: import org.tp23.antinstaller.input.OutputField;
046: import org.tp23.antinstaller.input.PasswordTextInput;
047: import org.tp23.antinstaller.input.SelectInput;
048: import org.tp23.antinstaller.input.TargetInput;
049: import org.tp23.antinstaller.input.TargetSelectInput;
050: import org.tp23.antinstaller.input.UnvalidatedTextInput;
051: import org.tp23.antinstaller.input.ValidatedTextInput;
052: import org.tp23.antinstaller.page.LicensePage;
053: import org.tp23.antinstaller.page.Page;
054: import org.tp23.antinstaller.page.ProgressPage;
055: import org.tp23.antinstaller.page.SimpleInputPage;
056: import org.tp23.antinstaller.page.SplashPage;
057: import org.tp23.antinstaller.page.TextPage;
058: import org.tp23.antinstaller.runtime.ConfigurationException;
059: import org.w3c.dom.Document;
060: import org.w3c.dom.Element;
061: import org.w3c.dom.NamedNodeMap;
062: import org.w3c.dom.Node;
063: import org.w3c.dom.NodeList;
064: import org.xml.sax.EntityResolver;
065: import org.xml.sax.InputSource;
066:
067: /**
068: * Loads the Ant Install configuration and sets the Installer object back
069: * into the context. A similar technique to the apache digester is used to
070: * populate object attributes in this class.
071: * N.B. The only types that can be set for object attributes are Strings, booleans
072: * or ints.
073: * @author Paul Hinds
074: * @version $Id: LoadConfigFilter.java,v 1.9 2007/01/28 08:44:39 teknopaul Exp $
075: */
076: public class LoadConfigFilter implements ExecuteFilter {
077:
078: //TODO move to InstallerContext
079: public static final String INSTALLER_CONFIG_FILE = "antinstall-config.xml";
080:
081: protected Installer installer = new Installer();
082: protected InstallerContext ctx;
083:
084: /**
085: * @see org.tp23.antinstaller.runtime.exe.ExecuteFilter#exec(org.tp23.antinstaller.InstallerContext)
086: */
087: public void exec(InstallerContext ctx) throws InstallException {
088: this .ctx = ctx;
089:
090: try {
091: installer = readConfig(ctx.getFileRoot(), ctx
092: .getInstallerConfigFile());
093: ctx.setInstaller(installer);
094: ctx.log("Config loaded");
095: } catch (IOException e) {
096: throw new InstallException(
097: "Not able to load and read the AntInstaller config",
098: e);
099: } catch (ConfigurationException e) {
100: throw new InstallException(
101: "Not able to load and read the AntInstaller config",
102: e);
103: }
104: }
105:
106: /**
107: * Currently read the config using any available XML parser
108: * This method reads the config from the file system
109: * @param fileRoot The directory where the config file is stored
110: * @param the name of the configuration file (usually antinstall-config.xml)
111: * @return Installer
112: */
113: public Installer readConfig(File fileRoot, String fileName)
114: throws IOException, ConfigurationException {
115:
116: installer.getResultContainer().setInstallRoot(fileRoot);
117:
118: File config = new File(fileRoot, fileName);
119: if (!config.exists()) { // passed in incorrectly on the command line or bad installer
120: throw new IOException();
121: }
122: InputSource xmlInp = new InputSource(
123: new FileInputStream(config));
124: readConfig(xmlInp);
125:
126: return installer;
127: }
128:
129: /**
130: * This overloaded method reads from the provided input stream to facilitate
131: * reading configs directly from the Jar, the file root is still needed
132: * for Ant's basedir. Used by InputStreamLoadConfigFilter
133: * @return Installer
134: */
135: protected Installer readConfig(File fileRoot,
136: InputStream configSource) throws IOException,
137: ConfigurationException {
138:
139: installer.getResultContainer().setInstallRoot(fileRoot);
140:
141: InputSource xmlInp = new InputSource(configSource);
142: readConfig(xmlInp);
143:
144: return installer;
145: }
146:
147: /**
148: * Currently read the config using any available XML parser
149: * @todo read the installer with only xerces
150: * @return Installer
151: */
152: protected Installer readConfig(InputSource xmlInp)
153: throws IOException, ConfigurationException {
154:
155: Document doc = null;
156: try {
157:
158: DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
159: .newInstance();
160: DocumentBuilder parser = docBuilderFactory
161: .newDocumentBuilder();
162:
163: // RFE [ 1475361 ] Using a custom entity resolver
164: String entityResolverClass = System
165: .getProperty("antinstaller.EntityResolverClass");
166: EntityResolver er = null;
167: if (entityResolverClass != null) {
168: try {
169: er = (EntityResolver) Class.forName(
170: entityResolverClass).newInstance();
171: } catch (Exception e) {
172: // Property error use default this is a very specific requirement
173: er = new CustomEntityResolver();
174: }
175: } else {
176: er = new CustomEntityResolver();
177: }
178:
179: parser.setEntityResolver(er);
180:
181: doc = parser.parse(xmlInp);
182: Element root = doc.getDocumentElement();
183: root.normalize();
184: setProperties(installer, root.getAttributes());
185:
186: NodeList mainPageNodes = root.getElementsByTagName("page");
187: Page[] mainPages = getPages(mainPageNodes);
188: installer.setPages(mainPages);
189:
190: NodeList langPageNodes = root
191: .getElementsByTagName("lang-page");
192: int numLangPages = langPageNodes.getLength();
193: if (numLangPages > 1) {
194: throw new InstallException(
195: "There can be only one lang-page");
196: } else if (numLangPages == 1) {
197: Page langPage = getLangPage(langPageNodes);
198: installer.setLangPage(langPage);
199: }
200:
201: } catch (Exception e) {
202: throw new IOException("DomFactory error: caused by:"
203: + e.getClass() + ":" + e.getMessage());
204: }
205: return installer;
206: }
207:
208: /**
209: * Used when reading the config
210: * @param allPages NodeList
211: * @throws ConfigurationException
212: */
213: private Page[] getPages(NodeList allPages)
214: throws ConfigurationException {
215: ArrayList pages = new ArrayList();
216: for (int i = 0; i < allPages.getLength(); i++) {
217: Element pageElem = (Element) allPages.item(i);
218: Page page = getPageType(pageElem.getAttribute("type"));
219: setProperties(page, pageElem.getAttributes());
220: pages.add(page);
221: getOutputFields(page, pageElem);
222: }
223: Page[] pageArr = new Page[pages.size()];
224: pages.toArray(pageArr);
225: return pageArr;
226: }
227:
228: /**
229: * Gets the optional language page
230: * @param allPages
231: * @return
232: * @throws ConfigurationException
233: */
234: private Page getLangPage(NodeList allPages)
235: throws ConfigurationException {
236: Element pageElem = (Element) allPages.item(0);
237: Page page = new SimpleInputPage();
238: setProperties(page, pageElem.getAttributes());
239: getOutputFields(page, pageElem);
240: return page;
241: }
242:
243: /**
244: * Used when reading the config
245: * @param page Page
246: * @param pageElem Element
247: * @throws ConfigurationException
248: */
249: private void getOutputFields(Page page, Element pageElem)
250: throws ConfigurationException {
251: page.setOutputField(getInnerOutputFields(pageElem));
252: }
253:
254: private OutputField[] getInnerOutputFields(Element elem)
255: throws ConfigurationException {
256: NodeList allFields = elem.getChildNodes();
257: ArrayList fields = new ArrayList();
258: for (int i = 0; i < allFields.getLength(); i++) {
259: if (!(allFields.item(i) instanceof Element))
260: continue;
261: Element fieldElem = (Element) allFields.item(i);
262: OutputField field = getOutputFieldType(fieldElem
263: .getNodeName(), fieldElem);
264: if (field != null) {
265: setProperties(field, fieldElem.getAttributes());
266: fields.add(field);
267: field
268: .setResultContainer(installer
269: .getResultContainer());
270: }
271: if (fieldElem.getFirstChild() != null
272: && (fieldElem.getFirstChild().getNodeType() == Node.TEXT_NODE || fieldElem
273: .getFirstChild().getNodeType() == Node.CDATA_SECTION_NODE)) {
274: String text = fieldElem.getFirstChild().getNodeValue();
275: text = text.trim();
276: if (text.length() > 0) {
277: field.setExplanatoryText(text);
278: }
279: }
280: }
281: OutputField[] fieldArr = new OutputField[fields.size()];
282:
283: return (OutputField[]) fields.toArray(fieldArr);
284: }
285:
286: /**
287: * Used when reading the config
288: * @param type String
289: * @throws ConfigurationException
290: * @return Page
291: */
292: private Page getPageType(String type) throws ConfigurationException {
293: if (type.equalsIgnoreCase("license")) {
294: return new LicensePage();
295: } else if (type.equalsIgnoreCase("input")) {
296: return new SimpleInputPage();
297: } else if (type.equalsIgnoreCase("progress")) {
298: return new ProgressPage();
299: } else if (type.equalsIgnoreCase("splash")) {
300: return new SplashPage();
301: } else if (type.equalsIgnoreCase("text")) {
302: return new TextPage();
303: }
304: throw new ConfigurationException("Unknown Page type:" + type);
305: }
306:
307: /**
308: * Used when reading the config
309: * @param type String
310: * @param field Element
311: * @throws ConfigurationException
312: * @return InputField
313: */
314: private OutputField getOutputFieldType(String type, Element field)
315: throws ConfigurationException {
316: if (type.equalsIgnoreCase("text")) {
317: return new UnvalidatedTextInput();
318: } else if (type.equalsIgnoreCase("directory")) {
319: return new DirectoryInput();
320: } else if (type.equalsIgnoreCase("target")) {
321: return new TargetInput();
322: } else if (type.equalsIgnoreCase("file")) {
323: return new FileInput();
324: } else if (type.equalsIgnoreCase("comment")) {
325: return new CommentOutput();
326: } else if (type.equalsIgnoreCase("checkbox")) {
327: return new CheckboxInput();
328: } else if (type.equalsIgnoreCase("validated")) {
329: return new ValidatedTextInput();
330: } else if (type.equalsIgnoreCase("ext-validated")) {
331: return new ExtValidatedTextInput();
332: } else if (type.equalsIgnoreCase("password")) {
333: return new PasswordTextInput();
334: } else if (type.equalsIgnoreCase("password-confirm")) {
335: return new ConfirmPasswordTextInput();
336: } else if (type.equalsIgnoreCase("hidden")) {
337: return new HiddenPropertyInput();
338: } else if (type.equalsIgnoreCase("date")) {
339: return new DateInput();
340: } else if (type.equalsIgnoreCase("app-root")) {
341: return new AppRootInput();
342: } else if (type.equalsIgnoreCase("conditional")) {
343: ConditionalField conditionalField = new ConditionalField();
344: OutputField[] outFields = getInnerOutputFields(field);
345: InputField[] inFields = new InputField[outFields.length];
346: for (int i = 0; i < outFields.length; i++) {
347: inFields[i] = (InputField) outFields[i];
348: }
349: conditionalField.setFields(inFields);
350: return conditionalField;
351: } else if (type.equalsIgnoreCase("select")) {
352: SelectInput sInput = new SelectInput();
353: NodeList allOptions = field.getElementsByTagName("option");
354: ArrayList options = new ArrayList();
355: for (int i = 0; i < allOptions.getLength(); i++) {
356: Element optionElem = (Element) allOptions.item(i);
357: SelectInput.Option option = sInput.getNewOption();
358: option.setText(optionElem.getAttribute("text"));
359: option.value = optionElem.getAttribute("value");
360: options.add(option);
361: }
362: SelectInput.Option[] optionArr = new SelectInput.Option[options
363: .size()];
364: options.toArray(optionArr);
365: sInput.setOptions(optionArr);
366:
367: return sInput;
368: } else if (type.equalsIgnoreCase("target-select")) {
369: TargetSelectInput sInput = new TargetSelectInput();
370: NodeList allOptions = field.getElementsByTagName("option");
371: ArrayList options = new ArrayList();
372: for (int i = 0; i < allOptions.getLength(); i++) {
373: Element optionElem = (Element) allOptions.item(i);
374: SelectInput.Option option = sInput.getNewOption();
375: option.setText(optionElem.getAttribute("text"));
376: option.value = optionElem.getAttribute("value");
377: options.add(option);
378: }
379: SelectInput.Option[] optionArr = new SelectInput.Option[options
380: .size()];
381: options.toArray(optionArr);
382: sInput.setOptions(optionArr);
383:
384: return sInput;
385: } else if (type.equalsIgnoreCase("large-select")) {
386: LargeSelectInput sInput = new LargeSelectInput();
387: NodeList allOptions = field.getElementsByTagName("option");
388: ArrayList options = new ArrayList();
389: for (int i = 0; i < allOptions.getLength(); i++) {
390: Element optionElem = (Element) allOptions.item(i);
391: LargeSelectInput.Option option = sInput.getNewOption();
392: option.setText(optionElem.getAttribute("text"));
393: option.value = optionElem.getAttribute("value");
394: options.add(option);
395: }
396: LargeSelectInput.Option[] optionArr = new LargeSelectInput.Option[options
397: .size()];
398: options.toArray(optionArr);
399: sInput.setOptions(optionArr);
400:
401: return sInput;
402: } else if (type.equalsIgnoreCase("lang-select")) {
403: LangSelectInput sInput = new LangSelectInput();
404: NodeList allOptions = field.getElementsByTagName("option");
405: ArrayList options = new ArrayList();
406: for (int i = 0; i < allOptions.getLength(); i++) {
407: Element optionElem = (Element) allOptions.item(i);
408: LangSelectInput.Option option = sInput.getNewOption();
409: option.setText(optionElem.getAttribute("text"));
410: option.value = optionElem.getAttribute("value");
411: options.add(option);
412: }
413: LangSelectInput.Option[] optionArr = new LangSelectInput.Option[options
414: .size()];
415: options.toArray(optionArr);
416: sInput.setOptions(optionArr);
417:
418: return sInput;
419: }
420: System.out.println("Unrecognised Input Element:" + type);
421: return null;
422: //throw new ConfigurationException("Unknown Input Field type:" + type);
423: }
424:
425: /**
426: * Calls bean setter methods based on attribures found. Could use BeanUtils here
427: * but we want to stay clear of external dependencies.
428: * @param bean Object
429: * @param map NamedNodeMap
430: */
431: private void setProperties(Object bean, NamedNodeMap map) {
432: int numAtts = map.getLength();
433: for (int a = 0; a < numAtts; a++) {
434: Node attribute = map.item(a);
435: String name = attribute.getNodeName();
436: String value = attribute.getNodeValue();
437: String methodName = "set"
438: + Character.toUpperCase(name.charAt(0))
439: + name.substring(1);
440: Method[] allMethods = bean.getClass().getMethods();
441: for (int m = 0; m < allMethods.length; m++) {
442: if (allMethods[m].getName().equals(methodName)) {
443: try {
444: Class[] parameters = allMethods[m]
445: .getParameterTypes();
446: Object[] paramValues;
447: if (parameters[0].equals(Boolean.class)) {
448: paramValues = new Boolean[1];
449: if (OutputField.isTrue(value)) {
450: paramValues[0] = Boolean.TRUE;
451: } else {
452: paramValues[0] = Boolean.FALSE;
453: }
454: } else if (parameters[0].equals(Integer.class)) {
455: paramValues = new Integer[1];
456: paramValues[0] = new Integer(value);
457: } else {
458: paramValues = new String[1];
459: paramValues[0] = value;
460: }
461: allMethods[m].invoke(bean, paramValues);
462: continue;
463: } catch (IndexOutOfBoundsException ex) {
464: // not the setter we are looking for
465: // this is the wrong overloaded method
466: continue;
467: }
468: // Ignore reflection errors and continue
469: catch (IllegalArgumentException e) {
470: } catch (IllegalAccessException e) {
471: } catch (InvocationTargetException e) {
472: }
473: }
474: }
475: }
476: }
477:
478: private static class CustomEntityResolver implements EntityResolver {
479: public InputSource resolveEntity(String publicId,
480: String systemId) {
481:
482: if (publicId
483: .equals("-//tp23 //DTD Ant Installer Config//EN")
484: && systemId
485: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.2.dtd")) {
486: InputSource localSrc = new InputSource(
487: this
488: .getClass()
489: .getResourceAsStream(
490: "/org/tp23/antinstaller/antinstall-config-0.2.dtd"));
491: return localSrc;
492: }
493: if (publicId
494: .equals("-//tp23 //DTD Ant Installer Config//EN")
495: && systemId
496: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.3.dtd")) {
497: InputSource localSrc = new InputSource(
498: this
499: .getClass()
500: .getResourceAsStream(
501: "/org/tp23/antinstaller/antinstall-config-0.3.dtd"));
502: return localSrc;
503: }
504: if (publicId
505: .equals("-//tp23 //DTD Ant Installer Config//EN")
506: && systemId
507: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.4.dtd")) {
508: InputSource localSrc = new InputSource(
509: this
510: .getClass()
511: .getResourceAsStream(
512: "/org/tp23/antinstaller/antinstall-config-0.4.dtd"));
513: return localSrc;
514: }
515: if (publicId
516: .equals("-//tp23 //DTD Ant Installer Config//EN")
517: && systemId
518: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.5.dtd")) {
519: InputSource localSrc = new InputSource(
520: this
521: .getClass()
522: .getResourceAsStream(
523: "/org/tp23/antinstaller/antinstall-config-0.5.dtd"));
524: return localSrc;
525: }
526: if (publicId
527: .equals("-//tp23 //DTD Ant Installer Config//EN")
528: && systemId
529: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.6.dtd")) {
530: InputSource localSrc = new InputSource(
531: this
532: .getClass()
533: .getResourceAsStream(
534: "/org/tp23/antinstaller/antinstall-config-0.6.dtd"));
535: return localSrc;
536: }
537: if (publicId
538: .equals("-//tp23 //DTD Ant Installer Config//EN")
539: && systemId
540: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.7.dtd")) {
541: InputSource localSrc = new InputSource(
542: this
543: .getClass()
544: .getResourceAsStream(
545: "/org/tp23/antinstaller/antinstall-config-0.7.dtd"));
546: return localSrc;
547: }
548: if (publicId
549: .equals("-//tp23 //DTD Ant Installer Config//EN")
550: && systemId
551: .equals("http://antinstaller.sf.net/dtd/antinstall-config-0.8.dtd")) {
552: InputSource localSrc = new InputSource(
553: this
554: .getClass()
555: .getResourceAsStream(
556: "/org/tp23/antinstaller/antinstall-config-0.8.dtd"));
557: return localSrc;
558: } else {
559: return null;
560: }
561: }
562: }
563: }
|