See http://docs.codehaus.org/display/IZPACK/Dynamic+Variables
Try first to set the checkonce=”true” attribute of your <dynamicvariables>.
Then try to switch to normal <variables> instead of <dynamicvariables>.
After all, why do you need your variables to be evaluated each panel change when you’re using the UserInputPanel?
I would understand if you were to depend on some kind of environmental condition, like the free RAM being under 50M or something more dynamic in nature.
~ Thank you,
Paul Bors
LoL I have precisely the same use-case. What you really want is to refresh the UserInputPanel once the user interacts with some form field.
We use MsSQL and Oracle and I would like to change the values of the variables depending on what the user selects from the DB Type drop-down as well.
Couple of things that come to mind, first off use <variables> since you can always update them by grabbing them from the InstallData via:
Properties installerProperties = installData.getVariables().getProperties();
For example, I extended the TargetPanel into my own custom one to allow for an upload configuration (properties) file to override my <variables> while at the same time refreshing the input for the install target text filed.
Given the following in install.xml (which I filter during the maven build):
<variables>
<variable name="TargetPanel.dir.windows" value="C:\MyProduct_${project.version}.${buildNumber}"/>
...
I then have my own TargetPanel which adds a second form field to browser for the configuration file and also grabs it via cmd line when invoked with –DmyConfig=c:\path\to\myConfig.properties. When the user selects the Browse button and selects the file I load the properties from the file on the disk and override my installer’s properties and then refresh the Target text field as well. So you can have user interaction and refresh your panel but it’s not out of the box.
This is my TargetPanel:
public class TargetPanel extends com.izforge.izpack.panels.target.TargetPanel {
private static final long serialVersionUID = 1L;
private FileSelectionPanel fileSelectionPanel;
public TargetPanel(Panel panel, InstallerFrame parent, GUIInstallData installData, Resources resources, Log log) {
super(panel, parent, installData, resources, log);
}
/**
* Add more components under the path input components.
*/
@Override
public void createLayoutBottom() {
add(createLabel("", "empty", LEFT, true), NEXT_LINE);
add(createLabel("infoCfg", "MyTargetPanel", "wizard", LEFT, true), NEXT_LINE);
// Create file selection components and add they to this panel.
fileSelectionPanel = new FileSelectionPanel(
this, installData, false,
System.getProperty("myCfg"), 71,
new ArrayList<ValidatorContainer>(),
"cfg", "My cfg properties file"
) {
private static final long serialVersionUID = 1L;
@Override
protected void fileChooserApproved() {
File selectedFile = fileSelectionPanel.getSelectedFile();
if(selectedFile != null) {
try {
PropertiesUtil.loadCustomProperties(selectedFile.getAbsolutePath(), installData);
// Adjust GUI specific installation path
String consoleInstallPath = installData.getVariables().getProperties().getProperty("console.installPath");
if((consoleInstallPath != null) && (consoleInstallPath.trim().length() > 0)) {
pathSelectionPanel.setPath(consoleInstallPath);
}
} catch(Throwable t) {
showCustomError(t);
}
}
}
};
fileSelectionPanel.setAllowEmptyInput(true);
add(fileSelectionPanel, NEXT_LINE);
add(createLabel("noteKnoaCfg", "KnoaTargetPanel", "flag", LEFT, true), NEXT_LINE);
super.createLayoutBottom();
}
...
I used my own PropertiesUtil helper class to update my installer variables because of the GUI, CLI and Automated installation mode (although automated installation mode read from the XML file). This is how the loadCustomProperties() method looks like:
/**
* Appends or updates the given {@link AutomatedInstallData}.
*
* @param fileName {@link File} name to read the properties from.
* @param installData {@link AutomatedInstallData} to update.
*/
public static void loadCustomProperties(String fileName, InstallData installData) {
if( (fileName != null) && (fileName.trim().length() > 0) ) {
logger.log(Level.INFO, "Using myCfg file name: " + fileName);
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(fileName));
Properties installerProperties = installData.getVariables().getProperties();
installerProperties.load(br);
String consoleInstallPath = installerProperties.getProperty("console.installPath");
if((consoleInstallPath != null) && (consoleInstallPath.trim().length() > 0)) {
installerProperties.setProperty("TargetPanel.dir.windows", consoleInstallPath);
}
updateCustomProperties(fileName, installData);
} catch (ObfuscatePropertyException lcpe) {
throw lcpe;
} catch (Throwable t) {
installData.setInstallSuccess(false);
logger.log(Level.SEVERE, t.toString(), t);
throw new ObfuscatePropertyException(fileName);
} finally {
if(br != null) {
try { br.close(); }
catch (IOException ioe) {
logger.log(Level.WARNING, ioe.toString(), ioe);
}
}
}
} else {
updateCustomProperties(null, installData);
}
}
/*
* Updates the given {@link AutomatedInstallData} by ensuring that obfuscating values take precedence.
*
* @param fileName Name of the properties file, or <code>null<code> if not available.
* @param installData {@link AutomatedInstallData} to update.
*/
private static void updateCustomProperties(String fileName, InstallData installData) {
// Obfuscated values take precedence (assume name convention of prefix.key and prefix.obfuscatedKey)
Properties installerProperties = installData.getVariables().getProperties();
Enumeration<?> e = installerProperties.propertyNames();
while(e.hasMoreElements()) {
String key = (String)e.nextElement();
if(key.contains(OBFUSCATED_MASK)) {
String[] keyTokens = key.split(OBFUSCATED_MASK);
if(keyTokens.length != 2) {
throw new ObfuscatePropertyException(fileName, key);
}
String clearTxtKey = keyTokens[0] + StringUtil.lowerFirst(keyTokens[1]);
String clearTxtVal = StringUtil.deobfuscate(installerProperties.getProperty(key));
installerProperties.setProperty(clearTxtKey, clearTxtVal);
}
}
}
Note that we obfuscate passwords in the properties file so they are not in clear text hence the little extra work.
Now, I default to MsSQL such as:
<variables>
...
<variable name="masterDS.type" value="MSSQL" /> <!-- Master DataSource -->
<variable name="masterDS.host" value="localhost" />
<variable name="masterDS.port" value="1433" />
<variable name="masterDS.database" value="knoa_master" />
<variable name="masterDS.usr" value="knoa_master" />
<variable name="masterDS.pwd" value="knoa_master" />
<variable name="masterDS.sysusr" value="" />
<variable name="masterDS.syspwd" value="" />
...
What’s cool about this is that I can have a masterDS.port=1234 in a properties file on disk and let the user load it on the TargetPanel. Our testing team loves this J
On the other hand I would need to toggle those default to the Oracle ones when the user changes the masterDS.type from MSSQL to ORACLE in the UI via the drop down in a similar way as the TargetPanel example only by extending the UserInputPanel into my own to register my own DB Type drop down listener and refresh the defaults as needed. But that’s more code since the UserInputPanel is a bit more complicated and I didn’t yet get to do it (I do have it on the road map).
I also think that there is a feature request recorded with izPack’s team to add this improvement (can’t find the Jira ticket now).
See also:
http://jira.codehaus.org/browse/IZPACK-773 “Unite variables and dynamic variables”
Have fun!
~ Paul Bors
Off the topic but something you might find useful is a DataSourceValidator at the panel level that “pings” the DB server for a valid connection.
We ping it by running a simple query on a known table of our schema, but this can be done much more generic (grab schema meta data, etc).
I found that most often users make typos so I added such a validator in my installer:
<panels>
...
<panel classname="com.knoa.console.installer.panels.userinput.UserInputPanel" id="userInputPanel_MasterDB">
<validator classname="com.knoa.console.installer.validators.MasterDataSourceValidator" />
</panel>
...
</panels>
This works by using the given masterDS.* variables from my previous e-mail below and the code is as follows:
import java.util.Map;
import com.izforge.izpack.api.data.InstallData;
import com.izforge.izpack.api.installer.DataValidator;
import com.izforge.izpack.installer.gui.IzPanel;
import com.***.util.database.DatabaseUtil;
import com.***.util.database.OracleDatabaseUtil;
import com.***.util.database.SqlServerDatabaseUtil;
/**
* {@link DataValidator} which can be assigned to a {@link IzPanel} to validate
* a data source settings of a given database.
*/
public abstract class DataSourceValidator implements DataValidator {
/**
* Method to validate an {@link InstallData}.
*
* @param installData@return {@link Status} the result of the validation
*/
@Override
public Status validateData(InstallData installData) {
DatabaseUtil dbUtil = getDatabaseUtil(installData);
if(dbUtil.validConnectionInfo(getDataSourceParams(installData), "")) {
return Status.OK;
}
return Status.ERROR;
}
/**
* Returns the string with messageId for an error
*
* @return String the messageId
*/
@Override
public String getErrorMessageId() {
return "DataSourceValidator.error.message";
}
/**
* Returns the string with messageId for a warning
*
* @return String the messageId
*/
@Override
public String getWarningMessageId() {
return null;
}
/**
* if Installer is run in automated mode, and validator returns a warning, this method is asked,
* how to go on
*
* @return boolean
*/
@Override
public boolean getDefaultAnswer() {
return false;
}
protected DatabaseUtil getDatabaseUtil(String dbType) {
if(dbType.equalsIgnoreCase("mssql")) {
return new SqlServerDatabaseUtil();
}
if(dbType.equalsIgnoreCase("oracle")) {
return new OracleDatabaseUtil();
}
return null;
}
/**
* {@link DatabaseUtil} that would perform the actual validation.
*
* @param installData The {@link InstallData}.
* @return {@link DatabaseUtil} that would attempt the db connection.
*/
protected abstract DatabaseUtil getDatabaseUtil(InstallData installData);
/**
* {@link Map} holding on to the {@link DataSource} parameters to validate.
*
* @param installData The {@link InstallData}.
* @return Map with the expected valus needed by the {@link DatabaseUtil} to perform the validation.
*/
protected abstract Map<String, String> getDataSourceParams(InstallData installData);
}
And the actual validator used by the Panel:
import java.util.Map;
import com.izforge.izpack.api.data.InstallData;
import com.***.installer.panels.glassfishconfig.GlassfishConfigPanelConsoleHelper;
import com.***.util.database.DatabaseUtil;
public class MasterDataSourceValidator extends DataSourceValidator {
@Override
protected DatabaseUtil getDatabaseUtil(InstallData installData) {
String dbType = installData.getVariable("masterDS.type");
return getDatabaseUtil(dbType);
}
@Override
protected Map<String, String> getDataSourceParams(InstallData installData) {
return GlassfishConfigPanelConsoleHelper.createParams(null, "master", installData);
}
}
Finally the CustomLangPack.eng.xml entry for the error message:
<izpack:langpack version="5.0"
...
<!-- DataSourceValidator strings -->
<str id="DataSourceValidator.error.message" txt="Cannot connect to the database given the provided settings. Make sure all fields are valid and that the database server can be reached."/>
</izpack:langpack>
I didn’t share this with the izPack community because of extensive use of our own DatabseUtils.
Feel free to take this and adopt it to be more generic and perhaps even contribute it back to the izPack community if you find it useful as we did for when there might be network policies in place that might prevent your host where you run the installer from reaching the DB server or etc.
Otherwise ignore this e-mail.
~ Thank you,
Paul Bors