Temply

Installation

  • Windows
    unzip temply-xxx-bin.zip
  • Linux
        tar -xvzf temply-xxx-bin.tar.gz

    you must ensure to make run.sh an executable (chmod +x run.sh)

    a subdirectory named "temply-xxx" will be created

Running

To run Temply, you need Java Runtime Environment Standard Edition 1.4 or above. You must run the run.bat or run.sh script depending on your OS, specifying the type of user interface to use, Swing (default) or console and the directory of the Temply project, optional for the Swing interface (you will be prompted).

For Swing:

./run.sh [temply project directory]

for console

./run.sh -console temply project directory

Sources

  • Windows
    unzip temply-xxx-src.zip
  • Linux
        tar -xvzf temply-xxx-src.tar.gz

    For compiling Temply you need Java JDK Standard Edition 1.5 or above, maven 2 (http://maven.apache.org) or above and Ant 1.5 or above (http://ant.apache.org). You also need an internet connection, for maven, to get requested jars from the maven repository. You can use maven to build and ant (default goal) to build the release files.

Tutorial

First of all, you must create a directory where to store your Temply project. Inside that directory, you must create a file named project.xml. Here is the one from the example:

<Project>
        <Template name="tomcat" file="tomcat.template">
                <Destination name="base" file="/opt/tomcat/webapps/app.xml"/>
        </Template>
</Project>

The scope of the project in this example, is to create a Tomcat application descriptor template, and to manage various kind of connection to the application database and various version of the application. I have created a template (tomcat.template in the same directory of the project) wich, when processed, will create a destination file app.xml in the same directory of the running tool (in production it will be the webapps directory under Tomcat intallation directory). As you can see the path must be specified with the UNIX (/) path separator.

Let we see how to define the template, here is the tomcat.template:

 <Context
        path="/app"
    docBase="${DocBase:docBase}">

        <Resource auth="Container"
                name="temply/repository.datasource"
                scope="Shareable"
                type="javax.sql.DataSource"/>
        <ResourceParams name="repository.datasource">
                <parameter><name>url</name><value>${Connection(Repository):url}</value></parameter>
                <parameter><name>username</name><value>${Connection(Repository):username}</value></parameter>
                <parameter><name>password</name><value>${Connection(Repository):password}</value></parameter>
                <parameter><name>driverClassName</name><value>${Connection(Repository):driverClassName}</value></parameter>
        </ResourceParams>

        <Resource name="main.datasource" 
                type="javax.sql.DataSource"
                auth="Container"
                scope="Shareable"/>
        <ResourceParams name="d0000.main.datasource">
                <parameter><name>url</name><value>${Connection(Main):url}</value></parameter>
                <parameter><name>driverClassName</name><value>${Connection(Main):driverClassName}</value></parameter>
                <parameter><name>username</name><value>${Connection(Main):username}</value></parameter>
                <parameter><name>password</name><value>${Connection(Main):password}</value></parameter>
        </ResourceParams>
        
</Context>

It's a simplified version (incomplete) of an application descriptor for Tomcat. As you can see, there are some markers in the template in the form:

${rule(comma separated tags):var}

Tags are not mandatory. In this example I have created two rules: DocBase and Connection, then for DocBase I have a var called docBase, for Connection I have url, driverClassName, username and password vars. I have used two tags to distinct the Main connection and the Repository connection.

Now I must define the rules to resolve the markers in the template. You must create a directory called rules, in the project directory, to store the rules. The rules, are .rule files written in the form of an xml file, each file can contain more than a rule, but this helps only in organize them, it has no special meaning.

In general, rules can resolve the vars defined in other rules and / or define vars wich will be resolved by other rules.

I'm planning to delete this step because I think can be deducted from the template, but for now you must define two rules wich describes the rules referenced in the template. So I have crated a base.rule file as follows:

<Rules>

        <Rule name="Connection">
                <Var name="url" type="String"/>
                <Var name="username" type="String"/>
                <Var name="password" type="String"/>
                <Var name="driverClassName" type="String"/>
        </Rule>
        
        <Rule name="DocBase">
                <Var name="docBase" type="String"/>
        </Rule>

</Rules>

This rules only defines vars, do not resolve nothing.

Then I have defined rules, for the docBase resolution. Here is the dirs.rule file:

<Rules>
        <Rule name="Head" >
                <Resolve rule="DocBase" var="docBase" value="~/head" />
        </Rule>
        
        <Rule name="Branch" >
                <Var name="version" type="String" />
                <Resolve rule="DocBase" var="docBase" value="~/branch_${Branch:version}" />
        </Rule>
</Rules>

The Head rule resolves completely the docBase var, specifying a simple value. The Branch rule, instead, defines a version var, and resolves the var docBase, with an expression wich contains itself a marker, in the same form you have seen in the template. It means that if you decide to use the Branch rule, you must then resolve the version var in order to resolve the docBase var, in this project the version is not resolved by static rules, bu will be asked by the script engine, as we can see further.

Then I have defined rules, for the two types of databases I need to configure. Here is the databases.rule file:

<Rules>
        <Rule name="SqlServer">
                <Var name="server" type="String"/>
                <Var name="port" type="Integer" default="1433"/>
                <Var name="database" type="String"/>
                
                <Resolve rule="Connection" var="url" value="jdbc:jtds:sqlserver://${SqlServer:server}:${SqlServer:port}/${SqlServer:database}" />
                <Resolve rule="Connection" var="driverClassName" value="net.sourceforge.jtds.jdbc.Driver" />
        </Rule>
        
        <Rule name="Oracle">
                <Var name="server" type="String"/>
                <Var name="port" type="Integer" default="1521"/>
                <Var name="sid" type="String"/>
        
                <Resolve rule="Connection" var="url" value="jdbc:oracle:thin:@${Oracle:server}:${Oracle:port}:${Oracle:sid}" />
                <Resolve rule="Connection" var="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        </Rule>
        
</Rules>

This rules both defines vars, but also resolves, completely or not, other vars, in the same way you have seen in the previous rules.

Then I have defined specific rules for Oracle in the oracle.rules:

<Rules>
        <Rule name="oracle.Dev">
                <Resolve rule="Oracle" var="sid" value="oracle8i" />
                <Resolve rule="Oracle" var="server" value="oracle8i" />
        </Rule>
        <Rule name="oracle.Repo">
                <Resolve rule="Connection" var="username" value="repoDB" />
                <Resolve rule="Connection" var="password" value="repoDB" />
        </Rule>
        <Rule name="oracle.Main">
                <Resolve rule="Connection" var="username" value="mainDB" />
                <Resolve rule="Connection" var="password" value="mainDB" />
        </Rule>
</Rules>

and I have defined specific rules for Sql Server in the sqlserver.rules:

<Rules>
        <Rule name="sqlserver.Dev" >
                <Resolve rule="Connection" var="username" value="sa" />
                <Resolve rule="Connection" var="password" value="sapwd" />
                <Resolve rule="SqlServer" var="server" value="sql2000dev" />
        </Rule>

        <Rule name="sqlserver.Customers" >
                <Resolve rule="Connection" var="username" value="cust_user" />
                <Resolve rule="Connection" var="password" value="cust_password" />
                <Resolve rule="SqlServer" var="server" value="sql-customers" />
        </Rule>

        <Rule name="sqlserver.Repo">
                <Resolve rule="SqlServer" var="database" value="repoDB" />
        </Rule>
        
        <Rule name="sqlserver.Main">
                <Resolve rule="SqlServer" var="database" value="mainDB" />
        </Rule>
</Rules>

Then I must define a script, in the Groovy scripting language, to prompt the user and apply the specified rules. You must create a directory called scripts, in the project directory, to store the scripts. You can have more than one script, if there's more than one, it will be asked to you by the command line tool. This is the Example.groovy file:

branch=chooser.choose("Branch", ["Head","Branch"])
resolver.add(branch)

if (branch == "Branch") {
        ver=chooser.getString("Version")
        resolver.addResolve("Branch", "version", ver)
}

println ""
println "DocBase"
println "----------"
println "docBase = " + resolver.resolve("\${DocBase:docBase}")
println ""

db=chooser.choose("Database", ["SqlServer","Oracle"])
type=chooser.choose("Type", ["Development","Production"])

if (db == "SqlServer") {
        resolver.add("SqlServer")
        if (type == "Development") {
                resolver.add("sqlserver.Dev")
        } else {
                resolver.add("sqlserver.Customers")
        }
        resolver.add("sqlserver.Repo", "Repository")
        resolver.add("sqlserver.Main", "Main")
} else {
        resolver.add("Oracle")
        if (type == "Development") {
                resolver.add("oracle.Dev")
        } else {
                throw new Exception("Unsupported type=" + type)
        }
        resolver.add("oracle.Repo", "Repository")
        resolver.add("oracle.Main", "Main")
}

println ""
println "Repository"
println "----------"
println "url = " + resolver.resolve("\${Connection(Repository):url}")
println "driver = " + resolver.resolve("\${Connection(Repository):driverClassName}")
println "username = " + resolver.resolve("\${Connection(Repository):username}")
println "password = " + resolver.resolve("\${Connection(Repository):password}")
println ""
println "Main"
println "----"
println "url = " + resolver.resolve("\${Connection(Main):url}")
println "driver = " + resolver.resolve("\${Connection(Main):driverClassName}")
println "username = " + resolver.resolve("\${Connection(Main):username}")
println "password = " + resolver.resolve("\${Connection(Main):password}")

In the script, you have access to two special variables, chooser and resolver. The chooser var is an helper, wich let you ask the user to choose a value from a list with the "choose" method or to get a string with "getString" method. The resolver is the core of the Temply engine and lets you add a rule with the method "add", specifying the rule name and an optional tag to restrict the rule to that tag, you can even resolve a var with the method "addResolve", specifyng the rule and var to resolve and the value. In the example script, I print the result of the vars, to show you the chance to resolve them and use that result to do some more evaluations, however at the end of the script the template will be parsed and transformed with the rules chosed added in the script.

The Groovy scripting language is a very powerful language and integrated with java libraries, and it can connect to databases via jdbc, parse XML and so on, in my the script I use at work, I connect to a "repository" database to retrieve the name of the application database name with wich resolve the template main connection. If you need some extra libraries to put in the classpath, in order to use them in the script, like jdbc drivers, you can put them in the lib directory of the Temply installation dir.

Finally, even if you can do everything with the script, it's better to use the rule files for static rules and the script to drive them.

Libraries