-->

Setting dynamic velocity properties for archetype

2019-08-24 07:37发布

问题:

I am creating an archetype and want to create a project wide available property containing things like the current date and an all lower case variant of the artifact id. I found the following post on Stackoverflow, that shows how it normally should be possible.

I tried adding this to the archetype-metadata.xml like so:

    ...
    <requiredProperty key="artifactIdLower">
        <defaultValue>${artifactId.toLowerCase()}</defaultValue>
    </requiredProperty>
    <requiredProperty key="ldt">
        <defaultValue>${package.getClass().forName("java.time.LocalDateTime").getMethod("now").invoke(null)}</defaultValue>
    </requiredProperty>
    <requiredProperty key="dtf">
        <defaultValue>${package.getClass().forName("java.time.format.DateTimeFormatter").getMethod("ofPattern", $package.getClass().forName("java.lang.String")).invoke(null, "dd MMM yyyy")}</defaultValue>
    </requiredProperty>
    <requiredProperty key="date">
        <defaultValue>${ldt.format($dtf)}</defaultValue>
    </requiredProperty>
    ...

The property artifactIdLower works like a charm, but the ldt, dtf and date don't seem to work, giving the error:

Null reference [template 'dtf', line 1, column 1] : ${package.getClass().forName("java.time.format.DateTimeFormatter").getMethod("ofPattern", $package.getClass().forName("java.lang.String")).invoke(null, "dd MMM yyyy")} cannot be resolved.

After that I tried to see where in the chain this null reference comes from. I was able to set pkgClass to $package.getClass() (or to $package.Class),

but after that I was unable to set $strClass to $package.getClass().forName("java.lang.String") or to $pkgClass.forName("java.lang.String") (funnily enough, both pkgClass and strClass should be a Class object).

This made me wonder if there is a restriction on using reflection inside the archetype-metadata.xml.

My question is: how can I set dynamically generated property values (like above) that can be used project wide?

I don't want to have to define these properties in every file that I create, because there might be more properties that I want to add later.

Edit: I tried to instead create a generalproperties.vm file that contained the #set directives. This file would then be loaded by every file on the first line using #parse("generalproperties.vm"). While I did get the file to be parsed from within the pom.xml file, it didnt behave as I wanted.

The following input

test
$null
#set( $ldtClass = $package.getClass().forName("java.time.LocalDateTime") )
$ldtClass.Name
$ldtClass.getMethod("now")
#set( $ldtNowMethod = $ldtClass.getMethod("now") )
$ldtNowMethod.Name
#set( $clsLoader = $package.getClass().getClassLoader() )
$clsLoader
#set( $ldtClass2 = $clsLoader.loadClass("java.time.LocalDateTime") )
$ldtClass2.Name
$ldtClass2.getMethod("now")
#set( $ldtNowMethod2 = $ldtClass2.getMethod("now") )
$ldtNowMethod2.Name
#set( $ldt = $ldtNowMethod2.invoke($null) )
$ldt
#set( $dtf = $package.getClass().forName("java.time.format.DateTimeFormatter").getMethod("ofPattern", $package.getClass().forName("java.lang.String")).invoke($null, "yyyy/MM/dd HH:mm:ss") )
$dtf

Generated the following output:

test
$null
java.time.LocalDateTime
$ldtClass.getMethod("now")
$ldtNowMethod.Name
$clsLoader
$ldtClass2.Name
$ldtClass2.getMethod("now")
$ldtNowMethod2.Name
$ldt
$dtf

The first 3 outputs are as expected, but after that I don't get the results I want. If someone is able to solve either of the above issues (with the metadata file or the generalproperties file), it would be highly appreciated.

回答1:

This should work:

...
<requiredProperty key="date">
    <defaultValue>${package.getClass().forName("java.time.LocalDateTime").getMethod("now").invoke(null).format($package.Class.forName("java.time.format.DateTimeFormatter").getMethod("ofPattern", $package.Class).invoke(null, "dd MMM yyyy"))}</defaultValue>
</requiredProperty>
...


回答2:

The only restriction I could think of is if you are using the SecureUberspector instead of the standard Uberspector in your Velocity configuration properties. But you wouldn't be able to get the java.time.LocalDateTime class, so it's not the case, unless maybe if you are using an old Velocity version. With Velocity 1.7 I am able to get a reference to the now() method and to call it.

Also, are you sure that the direct Java version is working properly? For instance, I read in the Class.getClassLoader() javadoc that "some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader."



回答3:

Maybe it could help somebody what I noticed.

When I had something like:

<requiredProperty key="packageId"></requiredProperty>
<requiredProperty key="packageIdInPathFormat">
    <defaultValue>${package.getClass().getMethod("replace").invoke($packageId, ".", "/")}</defaultValue>
</requiredProperty>

It was not evaluating expression in property packageIdInPathFormat.

But when I did something like:

<requiredProperty key="packageId"></requiredProperty>

<requiredProperty key="tempPackageId">
   <defaultValue>${packageId}</defaultValue>
</requiredProperty>

<requiredProperty key="packageIdInPathFormat">
   <defaultValue>${tempPackageId.replace(".", "/")}</defaultValue>
</requiredProperty>

Expression was evaluated.