001: /*
002: * Licensed under the Apache License, Version 2.0 (the "License");
003: * you may not use this file except in compliance with the License.
004: * You may obtain a copy of the License at
005: *
006: * http://www.apache.org/licenses/LICENSE-2.0
007: *
008: * Unless required by applicable law or agreed to in writing, software
009: * distributed under the License is distributed on an "AS IS" BASIS,
010: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: * See the License for the specific language governing permissions and
012: * limiations under the License.
013: */
014:
015: package org.tp23.antinstaller.runtime.exe;
016:
017: import java.io.File;
018: import java.io.FileInputStream;
019: import java.io.FileNotFoundException;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.util.ArrayList;
023: import java.util.Collections;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Properties;
027: import java.util.StringTokenizer;
028:
029: import org.tp23.antinstaller.InstallException;
030: import org.tp23.antinstaller.Installer;
031: import org.tp23.antinstaller.InstallerContext;
032: import org.tp23.antinstaller.PropertiesFileRenderer;
033: import org.tp23.antinstaller.input.ConditionalField;
034: import org.tp23.antinstaller.input.InputField;
035: import org.tp23.antinstaller.input.OutputField;
036: import org.tp23.antinstaller.input.PasswordTextInput;
037: import org.tp23.antinstaller.input.TargetInput;
038: import org.tp23.antinstaller.input.TargetSelectInput;
039: import org.tp23.antinstaller.input.OSSpecific;
040: import org.tp23.antinstaller.page.Page;
041: import org.tp23.antinstaller.renderer.AIResourceBundle;
042: import org.tp23.antinstaller.runtime.VersionHelper;
043: import org.tp23.antinstaller.runtime.exe.ExecuteRunnerFilter.AbortException;
044:
045: /**
046: * Loads properties from a file of default properties if found.
047: * the Installer element should define an attribute
048: * loadDefaults with one of the following values.
049: * <li>false - do not look for defaults</li>
050: * <li>prompt - look for properties and ask if they should be used if found</li>
051: * <li>true - look for defaults if found load them</li>
052: * <li>prompt-auto - weird case where installer permits zero user interaction running only from antinstaller-config.xml defaults</li>
053: *
054: * N.B. this is not a generic property loader but one specifically for properties files
055: * generated by a previous run of an identical installer or one that according to the version
056: * number is compatible, see PropertyTask for loading other property sets
057: * @author teknopaul
058: *
059: **/
060:
061: /*
062: * @TODO i18n for AbortExceptions
063: * @TODO need to revisit logic for property loading scenarios as not consistent
064: */
065:
066: public class PropertyLoaderFilter implements ExecuteFilter {
067:
068: private static final AIResourceBundle res = new AIResourceBundle();
069:
070: public static final String LOAD = "true";
071: public static final String PROMPT = "prompt";
072: public static final String PROMPT_AUTO = "prompt-auto";
073: public static final String FALSE = "false";
074: public static final String DEFAULT_PROPERTIES_FILE_PROPERTY = "antinstaller.properties";
075:
076: private final String fileNameProperty;
077:
078: private int definedPropertiesCount;
079:
080: /**
081: * Default constructor required for an ExecuteFilter implementation.
082: * The default property name given by @see{DEFAULT_PROPERTIES_FILE_PROPERTY}
083: * is used with this constructor
084: */
085: public PropertyLoaderFilter() {
086: this (DEFAULT_PROPERTIES_FILE_PROPERTY);
087: }
088:
089: /**
090: * Constructor that allows the name of the property containg the properties file
091: * to be specified
092: *
093: * @param fileNameProperty property containing the name of file
094: */
095: public PropertyLoaderFilter(final String fileNameProperty) {
096: this .fileNameProperty = fileNameProperty;
097: }
098:
099: /**
100: * Execute the filter action - in this case pre-setting InputField values
101: * with values loaded from a properties file (if present)
102: *
103: * @see org.tp23.antinstaller.runtime.exe.ExecuteFilter
104: * @param ctx context data
105: * @throws InstallException if an error occurred loading pre-defined properties
106: */
107: public void exec(InstallerContext ctx) throws InstallException {
108:
109: Installer installer = ctx.getInstaller();
110: String loadDefaults = installer.getLoadDefaults();
111: if (installer.isVerbose()) {
112: ctx.log("loadDefaults attribute:" + loadDefaults);
113: }
114: boolean load = false;
115: if (loadDefaults == null || FALSE.equals(loadDefaults)) {
116: if (installer.isVerbose()) {
117: ctx.log("Not loading defaults");
118: }
119: return;
120: }
121:
122: ctx.log("Checking for predefined properties");
123: Properties predefinedProps = loadPredefinedProperties(ctx,
124: fileNameProperty);
125: definedPropertiesCount = predefinedProps.size();
126:
127: boolean foundProps = false;
128: if (definedPropertiesCount == 0) {
129: ctx.log("No predefined properties");
130: } else {
131: foundProps = true;
132: }
133:
134: if (foundProps && PROMPT.equals(loadDefaults)) {
135: load = ctx.getMessageRenderer().prompt(
136: res.getString("prompt.load.defaults"));
137: } else if (foundProps && PROMPT_AUTO.equals(loadDefaults)) {
138: load = ctx.getMessageRenderer().prompt(
139: res.getString("prompt.load.defaults"));
140: } else if (foundProps && LOAD.equals(loadDefaults)) {
141: load = true;
142: }
143:
144: if ((!foundProps || !load) && ctx.isAutoBuild()
145: && PROMPT.equals(loadDefaults)) {
146: ctx.log("Cant run -auto install without properties");
147: throw new AbortException(
148: "Install Aborted: cant load ant.install.properties");
149: }
150:
151: if (load) {
152: if (installer.isVerbose()) {
153: ctx.log("Loading defaults");
154: }
155:
156: // version control
157: String propertiesVersion = predefinedProps
158: .getProperty(PropertiesFileRenderer.INSTALLER_VERSION_PROPERTY);
159: String configVersion = ctx.getInstaller().getVersion();
160: if (propertiesVersion != null) {
161: VersionHelper helper = new VersionHelper();
162: if ((!propertiesVersion.equals(configVersion))
163: && helper.equalOrHigher(configVersion,
164: propertiesVersion)) {
165:
166: // let major versions pass but prompt for differences
167: if ((!ctx.isAutoBuild())
168: && helper.majorVersionCompatible(
169: configVersion, propertiesVersion)) {
170: if (!ctx
171: .getMessageRenderer()
172: .prompt(
173: res
174: .getString("properties.version.mismatch"))) {
175: throw new AbortException(
176: "Install Aborted: existing configuration is not compatible, config version: "
177: + configVersion);
178: }
179: } else {
180: throw new AbortException(
181: "Install Aborted: existing configuration is not compatible, config version: "
182: + configVersion);
183: }
184: }
185:
186: } else {
187: throw new AbortException(
188: "Install Aborted: local ant.install.properties missing config version, must be equal or lower than: "
189: + configVersion);
190: }
191: // end version control
192:
193: handleDefaults(ctx, installer.getPages(), predefinedProps);
194:
195: } else if (PROMPT_AUTO.equals(loadDefaults)
196: && ctx.isAutoBuild()) {
197: ctx.log("Using XML defaults");
198: loadXmlDefaults(installer.getPages());
199: }
200: }
201:
202: /*
203: * Use the supplied properties to pre-populate the page fields
204: */
205: private void handleDefaults(InstallerContext ctx, Page[] allPages,
206: Properties props) throws InstallException {
207: for (int i = 0; i < allPages.length; i++) {
208: OutputField[] fields = allPages[i].getOutputField();
209: setInputValues(ctx, allPages[i], fields, props);
210: }
211: }
212:
213: private void setInputValues(InstallerContext ctx, Page page,
214: OutputField[] outputFields, Properties props)
215: throws InstallException {
216: //Should never happen, but guard against it
217: if (outputFields == null) {
218: return;
219: }
220:
221: // find relevant targets
222: String targets = props.getProperty(page.getName()
223: + PropertiesFileRenderer.TARGETS_SUFFIX);
224: List targetsList = splitTargets(targets);
225:
226: for (int j = 0; j < outputFields.length; j++) {
227: OutputField field = outputFields[j];
228:
229: if (field instanceof ConditionalField) {
230: ConditionalField condField = (ConditionalField) field;
231: setInputValues(ctx, page, condField.getFields(), props);
232: } else if (field instanceof InputField) {
233: InputField input = (InputField) field;
234: String propName = input.getProperty();
235: if (props.containsKey(propName)) {
236: String value = props.getProperty(propName);
237:
238: if (ctx.getInstaller().isDebug()) {
239: ctx.log("Setting " + propName + "=" + value);
240: }
241:
242: input.setDefaultValue(value); // does not evaluate references
243:
244: //Not convinced this is the best approach, but at least ensures default set for windoze
245: if (field instanceof OSSpecific) {
246: ((OSSpecific) field).setDefaultValueWin(value);
247: }
248: input.setInputResult(value);
249: input.setEditted(true);
250:
251: if (field instanceof PasswordTextInput) {
252: if (value == null) {
253: ctx
254: .getMessageRenderer()
255: .printMessage(
256: res
257: .getString("prompt.missing.default.password"));
258:
259: }
260: }
261:
262: // TARGET TYPES
263: if (field instanceof TargetInput) {
264: // Target and SelectTarget
265: TargetInput tgtInput = (TargetInput) field;
266: page.removeTarget(tgtInput.getIdx());
267: // if target was selected
268: if (!InputField.isFalse(value)) {
269: page.addTarget(tgtInput.getIdx(), tgtInput
270: .getTarget()); // returns the OS specific suffix if relevant
271: // DEBUG
272: if (!targetsList.contains(tgtInput
273: .getTarget())) {
274: // could be caused by someone trying to copy a file across platforms (not a good idea)
275: ctx
276: .log("Defaults error: targets list for page "
277: + page.getName()
278: + " should contain a TargetInput that was true");
279: }
280: } else {
281: if (InputField.isTrue(tgtInput.getForce())) {
282: String msg = "Defaults error: forced target for page "
283: + page.getName()
284: + " has been removed";
285: ctx.log(msg);
286: throw new InstallException(msg);
287: }
288: }
289: }
290: if (field instanceof TargetSelectInput) {
291: TargetSelectInput tgtInput = (TargetSelectInput) field;
292: page.removeTarget(tgtInput.getIdx());
293: // one target must be selected (what if the page was not shown??)
294: page.addTarget(tgtInput.getIdx(), value);
295: }
296: }
297: }
298: /*
299: * Properties that are present in properties file but which do not appear
300: * as an InputField should not be set in the ResultContainer.
301: * Additional properties should be loaded separately from additional
302: * resource files if there is a requirement for that using postDisplayTarget(s) - PH
303: */
304:
305: }
306:
307: //Page targets should be handled by the config loader process and indexed correctly
308: List pageTargets = page.getTargets(ctx);
309: Iterator iter = targetsList.iterator();
310: while (iter.hasNext()) {
311: String targetPerProps = (String) iter.next();
312: if (!pageTargets.contains(targetPerProps)) {
313: ctx.log("Defaults warning: targets list for page "
314: + page.getName() + " should contain "
315: + targetPerProps);
316: }
317: }
318:
319: }
320:
321: /**
322: * Check if external properties have been loaded
323: *
324: * @return <code>true</code> if an external properties file was configured and contained
325: * at least one property
326: */
327: protected boolean isPropertiesLoaded() {
328: return (definedPropertiesCount > 0);
329: }
330:
331: /*
332: * Primarily for unit testing
333: */
334: protected int getPropertiesFoundCount() {
335: return definedPropertiesCount;
336: }
337:
338: /**
339: * Load properties from a properties file if present.
340: * The name of the properties file is checked for in the following order.
341: * <p>
342: * If the parameter fileNamePropertyName is not null:
343: * <ul>
344: * <li>the environment is checked for an environment variable with that name</li>
345: * <li>java system properties are checked for a property with that name</li>
346: * </ul>
347: * If the file name has not been found, or if <code>fileNamePropertyName == null</code>
348: * then the default file name is used - @see{org.tp23.antinstaller.PropertiesFileRenderer#PROPERTIES_FILE_NAME}
349: *
350: * In embedded installer screen this method can be overriden to load properties
351: *
352: * @param context installer context
353: * @param fileNamePropertyName name of environment variable or java system property containing the
354: * name of the properties file to be loaded or <code>null</code>
355: * @return properties
356: * @throws InstallException if the properties file is missing or an error occurs loading it
357: */
358: protected Properties loadPredefinedProperties(
359: final InstallerContext context,
360: final String fileNamePropertyName) throws InstallException {
361:
362: Properties contextProps = InstallerContext.getEnvironment();
363: String propertiesFileName = null;
364: boolean failSilently = true;
365:
366: if (fileNamePropertyName != null) {
367: propertiesFileName = contextProps
368: .getProperty(InstallerContext.ENV_PREFIX
369: + fileNamePropertyName);
370:
371: if (propertiesFileName == null) {
372: propertiesFileName = contextProps
373: .getProperty(InstallerContext.JAVA_PREFIX
374: + fileNamePropertyName);
375: }
376:
377: if (propertiesFileName != null) {
378: //Properties have been passed explicitly to installer so must load them
379: failSilently = false;
380: }
381: }
382:
383: if (propertiesFileName == null) {
384: propertiesFileName = PropertiesFileRenderer.PROPERTIES_FILE_NAME;
385: }
386:
387: Properties definedProperties = new Properties();
388: File definedPropertiesFile = new File(propertiesFileName);
389:
390: context.log("Loading pre-defined properties from file "
391: + definedPropertiesFile.getAbsolutePath());
392:
393: try {
394: if (definedPropertiesFile.exists()) {
395: FileInputStream istream = new FileInputStream(
396: definedPropertiesFile);
397: definedProperties.load(istream);
398: istream.close();
399: } else {
400: InputStream istream = this .getClass()
401: .getResourceAsStream(propertiesFileName);
402: if (istream != null) {
403: definedProperties.load(istream);
404: istream.close();
405: } else {
406: if (!failSilently) {
407: throw new InstallException(
408: "Defined properties file "
409: + propertiesFileName
410: + " doesn't exist");
411: }
412: }
413: }
414: } catch (FileNotFoundException fnfExc) {
415: if (!failSilently) {
416: throw new InstallException("Defined properties file "
417: + definedPropertiesFile.getAbsolutePath()
418: + " doesn't exist");
419: }
420: } catch (IOException ioExc) {
421: if (!failSilently) {
422: throw new InstallException(
423: "Unable to read contents of defined properties file "
424: + definedPropertiesFile
425: .getAbsolutePath(), ioExc);
426: }
427: }
428:
429: definedPropertiesCount = definedProperties.size();
430:
431: if (context.getInstaller().isDebug()) {
432: logPropertiesLoaded(context, definedProperties,
433: definedPropertiesFile);
434: }
435:
436: return definedProperties;
437: }
438:
439: // Debug - log properties loaded
440: private void logPropertiesLoaded(final InstallerContext context,
441: final Properties properties, final File propertiesFile) {
442: Iterator iterator = properties.keySet().iterator();
443: context.log("Predefined properties (" + definedPropertiesCount
444: + ") loaded from " + propertiesFile.getAbsolutePath()
445: + "...");
446: while (iterator.hasNext()) {
447: String key = (String) iterator.next();
448: context.log(key + "=" + properties.getProperty(key));
449: }
450: }
451:
452: /*
453: * Could do a String.split(",") but want to avoid 1.4 specific stuff generally
454: * @param commaSeparated
455: * @return
456: */
457: private List splitTargets(String commaSeparated) {
458: if (commaSeparated == null) {
459: return Collections.EMPTY_LIST;
460: }
461: StringTokenizer st = new StringTokenizer(commaSeparated, ",");
462: List targets = new ArrayList();
463: while (st.hasMoreElements()) {
464: String element = st.nextToken();
465: if (element != null) {
466: element = element.trim();
467: if (element.length() > 0) {
468: targets.add(element.trim());
469: }
470: }
471: }
472: return targets;
473: }
474:
475: /*
476: * Loads the defaults from the antinstall-config.xml file
477: *
478: */
479: private void loadXmlDefaults(Page[] allPages) {
480: for (int i = 0; i < allPages.length; i++) {
481: OutputField[] fields = allPages[i].getOutputField();
482: for (int j = 0; j < fields.length; j++) {
483: if (fields[j] instanceof InputField) {
484: InputField iField = (InputField) fields[j];
485: iField.setInputResult(iField.getDefaultValue());
486: }
487: }
488: }
489:
490: }
491: }
|