Introduction: Webstart Install Wizards on Solaris
How should I package my product?
What code do I need to write?
Solaris Step-By-Step Example: SolarisBuilder.java
Sample Files Involved
Step 1: Layout out the Product Tree
Step 2: Begin the Builder
Step 3: Set the Product Name and Default Directory
Step 4: Set the Images to Display
Step 5: Set any Configurable Messages
Step 6: Build the Product Components
Step 7: Create Platform Objects
Step 8: Create Platform Dependencies
Step 9: Create Solaris Patch Components
Step 10: Create Solaris Package Components
Step 11: Build the Product Tree
Step 12: Create Configuration Panels
Step 13: Finish the Builder
Step 14: Compile & Run the Builder & Wizard
It is easy to create a Webstart Install Wizard for installing products on Solaris. Many products targeted for Solaris are complex and involve installation, configuration, and testing. Through the use of a Webstart Wizard, you can be sure that your product will get installed correctly and easily, without any confusion.Webstart Wizards make Solaris-specific installers through the support of Solaris SVR4 Packages and Solaris Patches. You can create a product tree of packages and patches, and have the Wizard install using this tree as a description of what to install. Your tree can be built to be architecture-independent, so that it works on all Solaris implementations, regardless of the architecture of the underlying machine.
Install vs. Configuration Wizards
The generic Wizard API allows you to create any type of wizard, including install wizards that install a software product, or a configuration wizard which configures a previously-installed product. However, you should not combine these functions into a single wizard, since the install wizard is intended to be run only once, but the configuration wizard might be run several times during the lifetime of the installed product.
If you would like to build wizard-style configuration into your product, you must build a separate wizard that can configure your product, and point your users to the wizard and how to run it once they install the product.
If your product already has an installation mechanism for Solaris (such as SVR4 Packages), creating a builder to install your product will be almost trivial. For new products that you are creating an installer for, any combination of Solaris SVR4 packages & patches and bare files can be installed using Webstart Wizards. However, Solaris SVR4 packages & patches are the only official way of distributing software on the Solaris platform. You should use Packages and Patches so that Solaris can recognize and register your product's files with the system.Each type of collection is explained in further detail below. You can even write support for your own custom type of files using the Wizard API (see the InstallLeaf API). You can also find examples of each type of collection in the example provided. See SolarisBuilder.java for the source code to this sample.
PkgUnitclass.
There is some extra
information you must provide to a PkgUnit so that it can
cleanly install your packages. This information is provided
at buildtime in your builder code. Please see
the API documentation for the PkgUnit class for information regarding how to specify a Package.
The only information you need to supply in your builder is
the relative path to the patch, and the revision. For example
../patches as the relative path, and 102341-03
as the revision. The patch is applied to the
system, and backup copies of the files being patched are made.
addInitializationTask() within
your builder).
"Product
Installation Directory:" question. With wizards, you
can create a series of user interface panels that gather
user customization information that you use to affect the
outcome of the install. Or, you could have a series of
panels that customizes the product after
installation, without the user having to go edit text files,
or run other utilities to finish the installation and
configuration of the product. You can then install these panels
within the panel sequence of your wizard to present to the user
before or after installation. There is an API to add these panels
in the Wizard API, see the SolarisBuilder below for an
example of a pre-install panel.
The top-level .install directory contains a
CD-level wizard that is capable of presenting a single
install for all of the products on the CD. In addition, the
installer script is located in the root
directory, which should run the install wizard. This is
provided so that users can easily locate and run the
install. There is a sample installer script (written in korn shell)
located in the samples directory. It has been used at Sun
to ship several multi-product CDs and can work for your product.
Each of the {product1, product2, . . . ., productN} directories contains all of the products to be installed with the CD-level installer, along with supporting files. Supporting files include another installer script that runs the product-level install (a wizard that only installs one product), the install wizard itself, the supporting localized resources, and the packages that make up the product.
You should use this layout for all CDs you install with Web Start Wizards.
For simple install wizards, the only thing you need to write is the builder code. This is where you will specify the files that comprise your product. You can also customize various aspects of the install, such as the "About..." message contents, the exit warning, etc. The rest of the install is written for you in theInstallArchiveWriterclass. A basic set of panels are provided for your wizard. The panel sequence is as follows:
- 1. Welcome (With optional "About..." Dialog)
- 2. Install Type ("Typical" or "Custom")
- 3. Locale Selection (if the user selected a "Custom" install)
- 4. Directory Selection (if the user selected a "Custom" install)
- 5. Component Selection (if the user selected a "Custom" install)
- 6. Verify Panel (Shows user what they selected)
- 7. Progress Panel (product actually gets installed here)
- 8. Summary Panel (shows results)
For more complex installs, you would need to build your product tree yourself, as well as configure the wizard panel tree. This is what we will be doing in the SolarisBuilder below. The basic steps to creating a wizard are:
- 1. Creating a development environment,
- 2. Writing a builder,
- 3. Creating pre- and post-installation configuration panels, and then
- 4. Compiling and running your builder.
It is recommended that you put any of your
supporting java .class files in a separate
package from the Webstart Wizards SDK classes. You
should, however, include the Webstart Wizards SDK classes
in your CLASSPATH setting. An example
directory structure that might be used is:
classes
--> com
---> sun
---> wizards
---> awt
---> core
---> panels
---> nodes
---> builder
---> rmi
---> install
---> products
---> panels
---> tasks
---> nodes
---> companyName
------> install
---> builders
---> panels
---> tasks
---> {other types}
You would then include the 'classes' directory in your CLASSPATH and
import your customized classes with import com.companyName.install.panels.*;
within your java code.
As an example, suppose you write a builder and place it in the com.companyName.install.builders
package and name it MyBuilder.java. To compile it, you would put the 'classes'
directory in your CLASSPATH and type:
javac com.companyName.install.builders.MyBuilder.java
Then, assuming it wrote your wizard to the current directory and
called it install.class, you would type java
install to run your wizard.
.class file for distribution. All builders (whether custom or not)
use the same basic skeleton builder to begin with:
public class SkeletonBuilder extends InstallArchiveWriter
{
public SkeletonBuilder()
{
super();
}
protected void createClientTree()
{
super.createClientTree();
/*
* Customize your builder here
*/
}
public static void main(String[] args)
{
SkeletonBuilder myBuilder = new SkeletonBuilder();
myBuilder.writeArchive();
}
}
You can then add extra code in the createClientTree() method to customize your
wizard.
In your builder's createClientTree() method, you can include calls to customize some or all
of your wizard, using InstallArchiveWriter's API provided. See the
InstallArchiveWriter API for
details of the methods available. Some of the things you can customize are:
addNode() method of
InstallArchiveWriter. A sample post-installation
configuration panel is used in the SolarisBuilder
below to configure the product after installation.
You should place your panel classes within your section of the development environment, separate from any Wizards SDK classes, and include the panels into your builder.
For example, suppose I have created a
post-configuration panel called "PostConfigurePanel.java" that prompts
for the sbin directory of the system. I have
placed the panel in the com.sun.myCompany.panels package. In my builder, I might
include this panel as a post-configuration panel by doing the following:
import com.sun.myCompany.panels.*;
[...]
PreconfigurePanel myPanel = new PreconfigurePanel("/usr/sbin");
addNode(PRE_VERIFY, myPanel);
This panel will then be shown to the user before the
VerifyPanel.
It is the panel's
responsiblility to correctly configure the system based on user input.
InstallArchiveWriter
can be used to create a fully-cusomized wizard that does
installation and configuration for your product. For a complete
example, go through the next section which presents a real-world
example and walks you through the entire process of creating a
customized wizard for a fictional product.
The following example builds a fictional product that consists of two patches, and two packages. The patches are required before the product will be installed, and the Java Source Demos will only be installed if the user has the Sun JDK version 1.1 on the system.
[SDK Base Directory]/solaris/samples/packages directory of the Webstart Wizards SDK.
These files are the ones you would have to provide if you were to build and distribute this product.
The product tree is the internal wizard representation of your product. It has nothing to do with the way the product files are organized on disk. The product tree is built at buildtime and then used to install at installtime (also known as runtime).
We first decide how we want to ship our product. We know we have to apply a patch first, and then install our product. We also have some demos we would like the customer to optionally be able to install, but only if they have a Sun JDK 1.1- compliant system.
The packages more, stuf, and demo have been built already and we cannot change them. more and stuf install on all Solaris platforms, so we do not need to worry about any platform dependencies. However, our two Solaris Patches must be installed differently depending on if we are on Solaris SPARC or Solaris Intel. Therefore, we place each patch underneath a platform dependency. This way, only one patch will be applied at install time. If we are on an Intel-based Solaris system, then patch 102342-04 will be applied. Otherwise, if we are on a SPARC-based Solaris system, then patch 102341-03 will be applied.
Therefore, our product tree conceptually looks like this:
[PRODUCT]
|
+----------------------------+-------------------------------+
| | |
[Required Patches]** [Sun Web Product]* [Demos]
| | |
| | |
+---+-------------+ +----+------------+ [JDK 1.1 Dependency]
| | | | |
| | [more] [stuf] |
[SPARC Dep.] | [demo]
| [X86 Dependency]
| |
| |
[102341-03] [102342-04]
NOTE:
** Required Component
* Optional Component
SolarisBuilder.java in the Web Start Wizards SDK
"classes" directory:
import com.sun.install.panels.*;
import com.sun.install.products.*;
import com.sun.wizards.core.*;
import com.sun.wizards.builder.resolver.*;
import java.util.*;
import java.io.*;
public class SolarisBuilder extends InstallArchiveWriter
{
public SolarisBuilder()
{
super();
}
protected void createClientTree()
{
/*
* Customization code goes ABOVE here
*/
super.createClientTree();
}
public static void main(String[] args)
{
SolarisBuilder sampleBuilder = new SolarisBuilder();
sampleBuilder.writeArchive();
}
}
This class extends InstallArchiveWriter so that we can use some of its
convenience methods created for making install wizards. We also override
createClientTree() because this is where we are going to make our wizard
do something instead of not doing anything.
You can compile and run this class if you wish. To compile it, type:
% javac SolarisBuilder.java
Do not type the '%'. That is an indication of a command prompt.
To run the resulting class, type:
% java SolarisBuilder
It will complain:
Nothing to do: No components added, quitting...
This is because we have not added any components, and could not create an installation wizard.
createClientTree method:
setProductName("Solaris Example Product");
setDefaultDirectory("[userDir]/myProduct");
The [userDir] key in the second line gets replaced at runtime with the "home" directory for the user
that is running the wizard. There are other substitutions one could do:
[productDir] = Standard install directory (/opt on Solaris)
[sharedLibDir] = Shared Library directory (/usr/lib on Solaris)
[tmpDir] = Temporary scratch directory (/tmp on Solaris)
[currentDir] = Current directory
createClientTree() method:
setImage("com.sun.install.install");
The com.sun.install.install specification means that the image exists in the com/sun/install
directory relative to your CLASSPATH (or current directory if no class path is set), and is called
install.gif, install.jpg, install.jpeg, or install.jfif. You must also make sure that your image gets put into the archive
by configuring a FileResolver to get the image, and adding the image to a ResourceCollection so that it gets resolved and put into the archive.
To do this, add this code to the createClientTree() method:
FileResolver resolver = new FileResolver(System.getProperty("java.class.path"));
ResourceCollection collection = new ResourceCollection();
collection.addResource("Images", "com.sun.install.install", resolver);
addCollection(collection);
super.createClientTree()line:
setAboutMsg(new Msg("com.sun.install.Install", "AboutText"));
setCancelMsg(new Msg("com.sun.install.Install", "CancelAreYouSure"));
setExitMsg(new Msg("com.sun.install.Install", "ExitAreYouSure"));
This sets some configurable messages. If you do not specify an
"About..." message, then the about button will not be displayed
on the Welcome Panel. The others will default to default
values. The About message is displayed when the user clicks the
"About" button on the Welcome Panel. The Cancel message is
displayed when the user cancels some operation such as install
or disk space checking, by clicking the "Cancel" button. The
Exit Message displays when the user clicks the "Exit" button.
Note that these messages are localized. At runtime, the specified locale resource bundle is searched according to the runtime locale. In this case the resource bundle "com.sun.install.Install" will be searched for a message corresponding to the supplied keys.
super.createClientTree()line:
SoftwareComponent requiredPatches = new SoftwareComponent(new Msg("Required Patches"));
SoftwareComponent webProduct = new SoftwareComponent(new Msg("Sun Web Product"));
SoftwareComponent demos = new SoftwareComponent(new Msg("Java Source Demos"));
A SoftwareComponent represents a logical software
component and can be selected and de-selected by the user.
Each one is given a name that will appear to the user on the Component Selection screen.
createClientTree() method:
Platform sparcPlatform = new Platform(Platform.ALL, Platform.SOLARIS, Platform.SPARC);
Platform x86Platform = new Platform(Platform.ALL, Platform.SOLARIS,Platform.X86);
These objects represent specific platforms (such as Sparc Solaris, or WindowsNT). These objects
can be used to build Platform Dependencies. See the API Documentation for instructions on how
to specify your own platform.
super.createClientTree()line:
PlatformDependency sparcDependency = new PlatformDependency(sparcPlatform, false);
PlatformDependency x86Dependency = new PlatformDependency(x86Platform, false);
SunJDKDependency JDK11Dependency = new SunJDKDependency(">=1.1", false);
These Platform Dependencies are objects inserted into the product tree.
They compare the supplied Platform object to the current platform and
"fail" if they do not match.
You can also invert the dependencies, which cause them to "pass"
when their dependencies "fail", by passing true as the second
argument to the constructor.
Note the specification of ">=1.1". This means Java version 1.1 or later.
These will be checked just before the installation takes place, and if the platforms do not match up, they will not install anything that is attached in the tree below that object.
You can create your own custom Dependency by creating a class that
extends com.sun.install.products.InstallNode and
implement the refresh() method. The refresh()
method should set the nodeIsActive property to Boolean(true) if
the dependency is true. Then you can insert your new Dependency into the
tree just as we have done here.
The SunJDKDependency is an example of how this might be done. You create one by passing the
version of the Sun JDK you are looking for. It has the following refresh()
method:
public boolean refresh(Vector targets, String sharedPoolKey)
{
RunCmd versionCheck = new RunCmd("/usr/bin/java -version", true);
String result = versionCheck.getAllOutput();
if ((result != null) && (isCompatible(result))
{
nodeIsActive = !invert;
}
else
{
nodeIsActive = false;
}
}
The isCompatible() method simply checks to see if the two version strings are compatible,
and is irrelevent to this topic.
Note that if the invert flag (a flag within the class) is true we
must invert the output; that is, set nodeIsActive to the opposite of what we would
normally set it to.
super.createClientTree()line:
PatchUnit sparcUsrPatch = new PatchUnit("../sparc", "102341-03");
PatchUnit x86UsrPatch = new PatchUnit("../sparc", "102342-04");
Creates our SPARC Solaris patch. We pass it the relative path to this
patch (which must exist at that relative path at runtime), and the revision number.
super.createClientTree()line:
Hashtable variables = new Hashtable();
variables.put("basedir", InstallConstants.currentInstallDirectory);
PkgUnit webPkg1 = new PkgUnit("../samples/solaris/packages", "more",
"../samples/solaris/packages/admin",
"../samples/solaris/packages/response",
variables, PkgUnit.BUILD_TIME);
PkgUnit webPkg2 = new PkgUnit("../samples/solaris/packages", "stuf",
"../samples/solaris/packages/admin",
"../samples/solaris/packages/response",
variables, PkgUnit.BUILD_TIME);
PkgUnit sunDemoPkg = new PkgUnit("../samples/solaris/packages", "demo",
"../samples/solaris/packages/admin",
"../samples/solaris/packages/response",
variables, PkgUnit.BUILD_TIME);
Creates our SVR4 package units.
These lines actually creates the PkgUnits. The arguments are as follows:
"../samples/solaris/packages" - The relative directory to the package.
At runtime, the package is located using this relative directory and
the package name. In this example, the package would be located at
../samples/solaris/packages/stuf.
"stuf" - The actual name of the SVR4 package.
"../samples/solaris/packages/admin" - The relative path to the "admin" files
for this product. Each SVR4 package can have an admin file in this
directory, with each admin file named the same as the name of the
package (stuf, in this case). The admin file is read during
initialization. It is then available to your wizard in the wizard
archive as a hashtable (name=value pairs). If you want
to change the admin file according to user input, you could create a
panel that takes user input, retrieves the admin file from the wizard
state, modifies a value, and writes it back. The wizard state key is
"admin.{package name}", in this case admin.stuf.
During installation, the admin file is written to a temporary directory and passed to pkgadd. If no admin file is specified here, or if the admin file cannot be found at runtime, a default admin file is generated (see the PkgUnit API documentation for more information on how this is done).
"../samples/solaris/packages/response" - The relative path to the "response"
directory containing the response files for this product.
Each SVR4 package can have a response file in
this directory, with each response file named the same as the name of
the package (stuf, in this case). The response file is read
during initialization. It is then available to your wizard in the
wizard archive as a hashtable (name=value pairs). If you
want to change the response file according to user input, you could
create a panel that takes user input, retrieves the response file from
the wizard state, modifies a value, and writes it back. The wizard
state key is "response.{package name}", in this case
response.stuf.
variables - The installation variables to pass to pkgadd.
This feature is new with the 3.0 PkgUnit class. See the
PkgUnit API Documentation for more details on this feature. Basically, SVR4 Packages can have dynamic
variables used in their pkgmap file. For instance, a package might have a line in it's pkgmap
such as:
1 f none $USERDIR/Demo1.java 0600 jhf staff 512000 0 909092253
This means that the file Demo1.java will be placed into the directory that is specified by the
USERDIR installation variable, which must exist in the environment during the pkgadd
invocation.
The variables hashtable passed to the PkgUnit constructor enumerates which variables must be set when
the wizard calls pkgadd for this package. For each key=value pair in the hashtable, the
key represents the variable name, and the value represents the Wizard State variable under which
the value for the variable is. The value is looked up, and the setting is placed into the response file if the value exists.
The special case is the basedir setting, which actually goes into the admin file. This setting
applies to all relocatable SVR4 packages.
If you want your package to relocate into the directory that the user types into the
DirectorySelectionPanel
then you must pass in a hashtable which has the entry:
variables.put("basedir", InstallConstants.currentInstallDirectory);
Which says "look up the string in the wizard state under the key InstallConstants.currentInstallDirectory, and place it
into the pkgadd environment as the setting for basedir. This is what we have done in this example.
PkgUnit.BUILD_TIME - Tells PkgUnit to collect and calculate package size information when building the wizard, rather than doing it at PkgUnit.RUN_TIME. Doing it at runtime allows you to build the wizard without having the actual package contents available. However, this incurs a small runtime performance penalty when the size information is calculated at runtime. If you have large packages, or many of them, it is advantageous (performance-wise) to do this at buildtime.
createClientTree() method:
sparcDependency.addComponent(sparcUsrPatch); x86Dependency.addComponent(x86UsrPatch); requiredPatches.addComponent(sparcDependency); requiredPatches.addComponent(x86Dependency); webProduct.addComponent(webPkg1); webProduct.addComponent(webPkg2); JDK11Dependency.addComponent(sunDemoPkg); demos.addComponent(JDK11Dependency); addComponent(requiredPatches, true, true); addComponent(webProduct); addComponent(demos);These lines build the tree from the bottom-up. First we create the "Required Patches" subtree, then the "Sun WebProduct" tree, and finally the "Demos" subtree. As we add each component to another component, the tree is formed.
The last 3 lines actually
add the fully-formed subtrees to the product itself. The first one (requiredPatches)
is specified to be required by passing true; that is, the user will not be able to de-select
this component. The others are optional.
createClientTree() method:
Object[] nameArgs = new Object[] {getProductName()};
DirectorySelectionPanel postConfigurePanel = new DirectorySelectionPanel(getWizardState(),
"Post Configuration Panel", "myDefaultDir",
"myDefaultDir", "/usr/lib");
postConfigurePanel.addDescriptionText(new Msg("com.sun.install.Install",
"PostConfigDirectoryDescription",
nameArgs));
postConfigurePanel.addLabelText(new Msg("com.sun.install.Install",
"PostConfigTextLabel"));
addNode(POSTINSTALL, postConfigurePanel);
This creates a single Directory Selection panel and places it as a
post-install panel. Post-install panels are shown after
installation. You can also place pre-install panels which
are shown directly before installation. The arguments to the DirectorySelectionPanel
constructor are:
wizardState - The buildtime wizard state, available from the superclass.
"Post Configuration Panel" - Default heading for this panel
"myDefaultDir" - The wizard state key under which is stored the default
directory displayed in the panel.
"myDefaultDir" (again) - The wizard state key under which this panel
should store the entry made by the user. This is stored via the isDisplayComplete()
method of the panel.
"/usr/lib" The default directory used during a panel reset,
normally this value is the same one stored under the default directory key.
Note that the messages added using addDescriptionText(..) and addLabelText(...)
are localized. At runtime, the specified
locale resource bundle is searched according to the runtime locale. In
this case the resource bundle "com.sun.install.Install" will be
searched for a message corresponding to the supplied keys.
The DescriptionText is the text displayed to the user that describes the data the panel is looking for.
The LabelText is the text displayed just above the text entry box describing the contents of the entry box.
The addNode() argument is the type of panel this is
(either PREINSTALL, POSTINSTALL,
POST_WELCOME, or PRE_VERIFY) and the root of the panel tree
itself, or a single panel if desired.
You could create an entire subtree of panels and insert them as post-install panels. If you did this, the entire tree would be visited after installation. In those panels, you could do post-install configuration of the newly-installed product according to user input. Or, you could do an electronic registration screen.
public static void main(String[] args)
{
SolarisBuilder sampleBuilder = new SolarisBuilder();
sampleBuilder.writeArchive();
System.exit(0);
}
This is the main method of your builder. This simply instantiates a builder
and writes it out to the current directory.
After this step, you should have a complete builder.
CLASSPATH
and doing the following (on Solaris):
$ unsetenv CLASSPATH $ cd [Web Start SDK Base Directory]/classes $ javac SolarisBuilder.javaIf there were no errors, you can now run your builder to create your wizard archive: $ java SolarisBuilder If there are no errors, your builder should have produced a
wizard.class file in the current directory.
This is your wizard! You can then run your wizard by typing:
$ java wizardYou should see your wizard come up with the Welcome Panel. If it does not, look for error messages that might explain why.
Since this example uses pkgadd and patchadd, you must run it with root priviledges. All
wizards created that use pkgadd and/or patchadd will have this requirement. To make this as easy
as possible, many people have created wrapper scripts that ask for the root password before executing the wizard.