[izpack-user] Dynamic variables usage

1,159 views
Skip to first unread message

Andrei Anishchenko

unread,
Jun 24, 2013, 2:30:07 AM6/24/13
to us...@izpack.codehaus.org
Hello all,

I am trying to make an installation process which relies on "Dynamic Variables". My installation description has a couple of UserInputPanels, after pressing of "Next" button some custom Validator is invoked, and data from particular panel is evaluated somehow. All controls that are subject to validation/evaluation are bound to certain dynamic variables, in order to provide them some default values depending on certain conditions.
It works with IzPack 4, but it does not work with 5.0.0-beta11. Attached please find a minimalistic sample of what I am doing, with no validators involved for simplicity. For me, values entered by user on first panel should populate corresponding fields on second one.

I made some debugging of IzPack source code, and it is clearly seen that values are actually set for corresponding variables if "Next" button is hit (see com.izforge.izpack.api.data.AutomatedInstallData#setVariable), but right after that some sort of variables refreshment is started, and these values are overwritten with initial ones (that is, ones that described in install.xml, see com.izforge.izpack.api.data.AutomatedInstallData#refreshVariables and com.izforge.izpack.core.data.DefaultVariables#refresh).

Apparently I am doing something wrong. Could somebody please advise is it my misunderstanding of concept or a bug?

Thank you in advance!
Dynamic.zip

Paul Bors

unread,
Jun 24, 2013, 3:03:57 PM6/24/13
to us...@izpack.codehaus.org

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

Andrei Anishchenko

unread,
Jun 25, 2013, 4:02:18 AM6/25/13
to us...@izpack.codehaus.org
Thank you for your reply.

I've tried checkonce=”true” option (it is mentioned in my attachment),
it did not work out. There is some tricky logic regarding this
"checkonce" option in
com.izforge.izpack.core.data.DynamicVariableImpl#evaluate, judging by
which I presume this is not what I need.

Let me elaborate my scenario. Our application (which is installed with
IzPack) has a set of databases supported. End-user should pick one
during installation process using combo-box, then hit "Next" button.
Next panel will show connection settings up, with certain values
pre-filled for user convenience, such as connection port. Depending on
user selection of particular database, this default value of port show
be populated with different values (for instance, if user picks MS
SQL, then port input box is pre-filled with 1433, whereas if user
picks MySQL, then it should be 3306; moreover, if user picks HSQL,
then username should be pre-filled "sa"). So here we have conditional
things, which I presume are not achievable with bare variables
("condition" attribute is useless for them, according to definition
given here: http://docs.codehaus.org/display/IZPACK/Variables). And
once user changes the default value of port, it should not be
overwritten with initial value. And it works with IzPack 4, but does
not with IzPack 5, since all dynamic variables are re-evaluated and
overwritten with initial values during each panel transition
(regardless of checkonce=”true”, as it might be seen from my
attachment).

This difference in dynamic variables behavior might be IzPack bug, but
still I am not that confident in this, so I will be very appreciate
for any help which might resolve this problem.
---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email


René Krell

unread,
Jun 25, 2013, 4:40:35 AM6/25/13
to us...@izpack.codehaus.org
Hi Andrei,

my quick recommendation is using variables, not dynamic variables:
    <variables>
        <variable name="variable1" value="" />
        <variable name="variable2" value=""/>
    </variables>
There is no need for dynamic variables in the test case you sent, if you just want to initialize them.

Regarding the checkonce="true" in this case, I would have a look at it. In fact this should evaluate just once. You can file an issue in Jira for this to not forget about it, this would be helpful.

Thank you,
René



2013/6/25 Andrei Anishchenko <andrew.a...@gmail.com>

Paul Bors

unread,
Jun 25, 2013, 12:42:52 PM6/25/13
to us...@izpack.codehaus.org

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

Paul Bors

unread,
Jun 25, 2013, 12:55:42 PM6/25/13
to us...@izpack.codehaus.org

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

 

Reply all
Reply to author
Forward
0 new messages