You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@velocity.apache.org by Christopher Schultz <ch...@christopherschultz.net> on 2012/07/11 18:13:13 UTC

Odd observation during debugging

All,

I recently started getting intermittent OOMEs in my webapp that seem to
be completely recoverable. The stack traces look like this:

java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2882)
        at
java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
        at
java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390
)
        at java.lang.StringBuilder.append(StringBuilder.java:119)
        at java.lang.StringBuilder.append(StringBuilder.java:115)
        at
java.util.AbstractCollection.toString(AbstractCollection.java:422)
        at
org.apache.velocity.runtime.parser.node.ASTReference.evaluate(ASTReference.java:547)
    [...]

This happens so rarely, it's hard to catch, and it's fairly obvious what
is happening: toString is being called on a really huge List. We try not
to use really huge lists, but I suppose there is some potential danger
in rare instances. I want to find those instances.

Velocity 1.7, Tools 2.0 (plus some committed bugfixes that haven't made
it into an official release, yet).

So I thought I'd add some trickery to my VelocityContexts: We use our
own subclasss of VelocityLayoutServlet for a number of things, and I
have overridden the createContext() to wrap the Context the
super.createContext() returns in a ChainedContext (btw, that's
deprecated but doesn't document a replacement -- what should I use?). My
wrapper wraps all List and Set objects in yet another set of wrappers
which essentially pass-through everything except that their toString
methods throw an exception: there should be no reason that a List or Set
should have toString() called on it, because they should only be
iterated over, for instance.

But right on my login page, I see this exception after such instrumentation:

SEVERE: Servlet.service() for servlet velocity threw exception
org.apache.velocity.exception.VelocityException: Reference evaluation
threw an exception at /layout/header.vm[line 18, column 5]
        at
org.apache.velocity.runtime.parser.node.ASTReference.evaluate(ASTRefe
rence.java:551)
        at
org.apache.velocity.runtime.parser.node.ASTExpression.evaluate(ASTExp
ression.java:62)
        at
org.apache.velocity.runtime.parser.node.ASTIfStatement.render(ASTIfSt
atement.java:85)
        at
org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.
java:342)
        at
org.apache.velocity.runtime.directive.Parse.render(Parse.java:260)
        at
org.apache.velocity.runtime.parser.node.ASTDirective.render(ASTDirect
ive.java:207)
        at
org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.
java:342)
        at org.apache.velocity.Template.merge(Template.java:356)
        [....]
Caused by: java.lang.IllegalStateException: Request /myapp/login.vm,
called toString on key 'extraStylesheets'
        at
com.chadis.web.servlet.VelocityLayoutServlet$WrappedList.toString(VelocityLayoutServlet.java:220)
        at
org.apache.velocity.runtime.parser.node.ASTReference.evaluate(ASTReference.java:547)

If I look at everything that references the key 'extraStylesheets' in my
login page (and the header, etc.), I get only these:

login.vm only includes these references:
#set($extraStylesheets = ['/includes/css/login.css'])

I'm using VelocityLayoutServlet which is using my layouts/Default.vm (no
references to 'extraStylesheets') which includes (via #parse)
'header.vm' which has this to say:

#if($extraStylesheets)
	#foreach($stylesheet in $extraStylesheets)
    <link type="text/css" href="$link.setRelative($stylesheet)"
rel="stylesheet" />
	#end
#end

...and that's it. Given the upper stack trace it seems that the #if() is
evaluating the argument by converting it into a String for evaluation.
Is that intentional? I always used "#if($foo)" to check to see if $foo
had a value of any kind -- that is, if it was non-null (and non-false)
but it appears that the value is being converted into a String for ...
what purpose?

I know a lot of changes have occurred over the years and that
String-conversion is something that has seen some of that action. Is
there a "better" way of determining if a List (or whatever) has a value?
That is, without converting it to a String, first?

One can argue that I shouldn't have Lists of objects so large that
converting them to a String will cause an OOME, but the calling of
toString on these Lists is certainly surprising.

Thanks,
-chris


Re: Odd observation during debugging

Posted by Nathan Bubna <nb...@gmail.com>.
On Mon, Jul 16, 2012 at 9:04 AM, Christopher Schultz
<ch...@christopherschultz.net> wrote:
> Nathan,
>
> On 7/11/12 1:35 PM, Nathan Bubna wrote:
>> On Wed, Jul 11, 2012 at 9:58 AM, Christopher Schultz
>> <ch...@christopherschultz.net> wrote:
>>> Nathan,
>> ...
>>>> See VELOCITY-731 and VELOCITY-692 and others...
>>>
>>> Those were good to read: 1.6 introduced more consistent (if slower)
>>> treatment of references and then a setting was added to get the old
>>> behavior.
>>>
>>> Any idea if a future version of Velocity (maybe 2.x) will have the
>>> default value of that directive set to "false" so that toString will
>>> only be called for users who actually want that?
>>>
>>> While backward-compatibility is certainly a reasonable goal, this
>>> behavior kind of sucks... and I can't really think of too many cases
>>> where it makes sense. Good that there is a setting for it ;)
>>
>> Actually, i still consider it correct for a template language (which
>> is not the same as a scripting language) to treat render-as-empty as
>> false in an #if.  So, my hope for 2.0 was to not to remove that
>> behavior, but to pre-empt it.
>
> On a somewhat related issue, we had been considering using "strict
> reference mode" except that -- at least using the current if-directive
> semantics, it's not practical to do things like this:
>
> #if($mymap && $mymap.containsKey('somekey') &&
> !$mymap.get('somekey').isEmpty() && ...)
>
> ...because many of those individual parts of the predicate may end up
> returning something that is neither Boolean nor a Map, etc. and you
> *must* test them for non-null-ness before trying to use them, otherwise
> you risk an exception.
>
> Without disabling the 'toString' behavior in the configuration, there
> will be no way for these types of checks to execute without spewing lots
> of (in my case) useless String objects (not to mention the wasted time
> to generate them in the first place).
>
> I've seen the use of things like this on occasion:
>
> #if($myobject == $null)
>
> My understanding was that a null reference would never be considered
> equal to *anything*, even another null reference. Does the above
> actually work? And the "$null" is just a reference that is assumed to be
> null, right? When in strict-reference-mode, would that work since $null
> has (presumably) never been defined?

Yes, it works.  And yes, $null is a reference assumed to be null.  And
i believe strict mode is concerned about key existence, so just do:

context.put("null", null);

And you should be able to use that.

---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscribe@velocity.apache.org
For additional commands, e-mail: user-help@velocity.apache.org


Re: Odd observation during debugging

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Nathan,

On 7/11/12 1:35 PM, Nathan Bubna wrote:
> On Wed, Jul 11, 2012 at 9:58 AM, Christopher Schultz
> <ch...@christopherschultz.net> wrote:
>> Nathan,
> ...
>>> See VELOCITY-731 and VELOCITY-692 and others...
>>
>> Those were good to read: 1.6 introduced more consistent (if slower)
>> treatment of references and then a setting was added to get the old
>> behavior.
>>
>> Any idea if a future version of Velocity (maybe 2.x) will have the
>> default value of that directive set to "false" so that toString will
>> only be called for users who actually want that?
>>
>> While backward-compatibility is certainly a reasonable goal, this
>> behavior kind of sucks... and I can't really think of too many cases
>> where it makes sense. Good that there is a setting for it ;)
> 
> Actually, i still consider it correct for a template language (which
> is not the same as a scripting language) to treat render-as-empty as
> false in an #if.  So, my hope for 2.0 was to not to remove that
> behavior, but to pre-empt it.

On a somewhat related issue, we had been considering using "strict
reference mode" except that -- at least using the current if-directive
semantics, it's not practical to do things like this:

#if($mymap && $mymap.containsKey('somekey') &&
!$mymap.get('somekey').isEmpty() && ...)

...because many of those individual parts of the predicate may end up
returning something that is neither Boolean nor a Map, etc. and you
*must* test them for non-null-ness before trying to use them, otherwise
you risk an exception.

Without disabling the 'toString' behavior in the configuration, there
will be no way for these types of checks to execute without spewing lots
of (in my case) useless String objects (not to mention the wasted time
to generate them in the first place).

I've seen the use of things like this on occasion:

#if($myobject == $null)

My understanding was that a null reference would never be considered
equal to *anything*, even another null reference. Does the above
actually work? And the "$null" is just a reference that is assumed to be
null, right? When in strict-reference-mode, would that work since $null
has (presumably) never been defined?

Thanks,
-chris


Re: Odd observation during debugging

Posted by Nathan Bubna <nb...@gmail.com>.
On Wed, Jul 11, 2012 at 11:05 AM, Christopher Schultz
<ch...@christopherschultz.net> wrote:
> Nathan,
>
> On 7/11/12 1:35 PM, Nathan Bubna wrote:
>> Actually, i still consider [the toString behavior] correct for a template language (which
>> is not the same as a scripting language) to treat render-as-empty as
>> false in an #if.  So, my hope for 2.0 was to not to remove that
>> behavior, but to pre-empt it.  On your large lists, it would never get
>> around to checking toString(), since it would find isEmpty() and use
>> that.  IMHO, the lookup order ought to be something like this:
>>
>> #if( $obj ) will be false if:
>>
>> $obj is null
>> $obj is boolean false
>> $obj returns false from getAsBoolean()
>> $obj is empty string (CharSequence w/length 0)
>> $obj returns true from isEmpty()
>> $obj is array of length 0
>> $obj returns null from getAsString()
>> $obj returns empty string from getAsString()
>> $obj returns null from getAsNumber()
>> $obj returns empty string from toString()
>
> If the ordering of the checking is really done that way, then my huge
> lists (which shouldn't exist in the first place, really -- I know that
> part's my fault) would fall-through at case #5 (isEmpty)? It's not
> entirely clear to me what happens in this (List) case.

yes. no object that implements isEmpty() (which would include all
Collections and Maps) would continue checking beyond that step.

>> So the config switch would stay, but in far more cases, it would be
>> unnecessary.  The vast majority of slow toString() impls are
>> collections or maps.  Other custom objects (see Apache Click) would
>> easily be able to avoid toString() by having an isEmpty() or
>> getAsBoolean() method.
>
> And these would be introspected so they don't have to implement some
> kind of interface, right?

definitely.  VTL has pushed toward duck-typing for years now and
should continue to do so, IMNSHO.

> After banging my head against my own code trying to figure out why my
> non-template-defined Lists weren't being wrapped, I realized that the
> contents of the Request/Session/Application attributes are *not* copied
> into the Context... they are fetched when requested. That means my code
> needs to get a bit more complicated if I want to wrap collections that
> are fetched from the request attributes ;)

true.

> This code is really crappy... but do you think it would be useful for
> anyone? I'd be happy to post it somewhere.

i have no idea.

> Thanks,
> -chris
>

---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscribe@velocity.apache.org
For additional commands, e-mail: user-help@velocity.apache.org


Re: Odd observation during debugging

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Nathan,

On 7/11/12 1:35 PM, Nathan Bubna wrote:
> Actually, i still consider [the toString behavior] correct for a template language (which
> is not the same as a scripting language) to treat render-as-empty as
> false in an #if.  So, my hope for 2.0 was to not to remove that
> behavior, but to pre-empt it.  On your large lists, it would never get
> around to checking toString(), since it would find isEmpty() and use
> that.  IMHO, the lookup order ought to be something like this:
> 
> #if( $obj ) will be false if:
> 
> $obj is null
> $obj is boolean false
> $obj returns false from getAsBoolean()
> $obj is empty string (CharSequence w/length 0)
> $obj returns true from isEmpty()
> $obj is array of length 0
> $obj returns null from getAsString()
> $obj returns empty string from getAsString()
> $obj returns null from getAsNumber()
> $obj returns empty string from toString()

If the ordering of the checking is really done that way, then my huge
lists (which shouldn't exist in the first place, really -- I know that
part's my fault) would fall-through at case #5 (isEmpty)? It's not
entirely clear to me what happens in this (List) case.

> So the config switch would stay, but in far more cases, it would be
> unnecessary.  The vast majority of slow toString() impls are
> collections or maps.  Other custom objects (see Apache Click) would
> easily be able to avoid toString() by having an isEmpty() or
> getAsBoolean() method.

And these would be introspected so they don't have to implement some
kind of interface, right?

After banging my head against my own code trying to figure out why my
non-template-defined Lists weren't being wrapped, I realized that the
contents of the Request/Session/Application attributes are *not* copied
into the Context... they are fetched when requested. That means my code
needs to get a bit more complicated if I want to wrap collections that
are fetched from the request attributes ;)

This code is really crappy... but do you think it would be useful for
anyone? I'd be happy to post it somewhere.

Thanks,
-chris


Re: Odd observation during debugging

Posted by Nathan Bubna <nb...@gmail.com>.
On Wed, Jul 11, 2012 at 9:58 AM, Christopher Schultz
<ch...@christopherschultz.net> wrote:
> Nathan,
...
>> See VELOCITY-731 and VELOCITY-692 and others...
>
> Those were good to read: 1.6 introduced more consistent (if slower)
> treatment of references and then a setting was added to get the old
> behavior.
>
> Any idea if a future version of Velocity (maybe 2.x) will have the
> default value of that directive set to "false" so that toString will
> only be called for users who actually want that?
>
> While backward-compatibility is certainly a reasonable goal, this
> behavior kind of sucks... and I can't really think of too many cases
> where it makes sense. Good that there is a setting for it ;)

Actually, i still consider it correct for a template language (which
is not the same as a scripting language) to treat render-as-empty as
false in an #if.  So, my hope for 2.0 was to not to remove that
behavior, but to pre-empt it.  On your large lists, it would never get
around to checking toString(), since it would find isEmpty() and use
that.  IMHO, the lookup order ought to be something like this:

#if( $obj ) will be false if:

$obj is null
$obj is boolean false
$obj returns false from getAsBoolean()
$obj is empty string (CharSequence w/length 0)
$obj returns true from isEmpty()
$obj is array of length 0
$obj returns null from getAsString()
$obj returns empty string from getAsString()
$obj returns null from getAsNumber()
$obj returns empty string from toString()

So the config switch would stay, but in far more cases, it would be
unnecessary.  The vast majority of slow toString() impls are
collections or maps.  Other custom objects (see Apache Click) would
easily be able to avoid toString() by having an isEmpty() or
getAsBoolean() method.

---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscribe@velocity.apache.org
For additional commands, e-mail: user-help@velocity.apache.org


Re: Odd observation during debugging

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Nathan,

On 7/11/12 12:28 PM, Nathan Bubna wrote:
> Just set:
> directive.if.tostring.nullcheck = false

As always, thanks for the quick, response. I had gotten this far in my
code reading:

ASTReference.evaluate():
    public boolean evaluate(InternalContextAdapter context)
        throws MethodInvocationException
    {
        Object value = execute(null, context);

        if (value == null)
        {
            return false;
        }
        else if (value instanceof Boolean)
        {
            if (((Boolean) value).booleanValue())
                return true;
            else
                return false;
        }
        else if (toStringNullCheck)
        {
            try
            {
                return value.toString() != null;
            }
            catch(Exception e)
            {
                throw new VelocityException("Reference evaluation threw
an exception at "
                    + Log.formatFileString(this), e);
            }
        }
        else
        {
            return true;
        }
    }

I was almost there when you wrote ;)

> See VELOCITY-731 and VELOCITY-692 and others...

Those were good to read: 1.6 introduced more consistent (if slower)
treatment of references and then a setting was added to get the old
behavior.

Any idea if a future version of Velocity (maybe 2.x) will have the
default value of that directive set to "false" so that toString will
only be called for users who actually want that?

While backward-compatibility is certainly a reasonable goal, this
behavior kind of sucks... and I can't really think of too many cases
where it makes sense. Good that there is a setting for it ;)

Thanks,
-chris


Re: Odd observation during debugging

Posted by Christopher Schultz <ch...@christopherschultz.net>.
All,

On 12/31/12 7:58 AM, Christopher Schultz wrote:
> Nathan,
> 
> On 7/11/12 12:28 PM, Nathan Bubna wrote:
>> Just set:
>> directive.if.tostring.nullcheck = false
> 
> We just went into production last night after testing in this
> configuration for months, and we're getting this bizarre error message
> that filled-up our logs files and the whole disk on the server.
> 
> [...]
>
> Caused by: java.lang.ClassCastException:
> 'directive.if.tostring.nullcheck' doesn't map to a Boolean object

So this turned out to be a birazzely-merged properties file that
contained these lines:

# Turn off template re-loading
webapp.resource.loader.modificationCheckInterval=0
<<<<<<< .mine

# Disable the use or toString to determine 'truth' of references
directive.if.tostring.nullcheck=false
=======

# Set output encoding
output.encoding=UTF-8

# Disable convert-to-string for truth-checking in #if directive
directive.if.tostring.nullcheck=false
>>>>>>> .r11696

So, for whatever reason, the above confused the heck out of either the
property-file parser or the code that tries to test the peroperty's value.

Sor, this was entirely our fault and had nothing to do with Velocity per
se though it would have been nice if an insane value had issued a single
ERROR or WARN and then silently used the default rather than spewing
gigabytes of stack traces ;)

-chris


Re: Odd observation during debugging

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Nathan,

On 7/11/12 12:28 PM, Nathan Bubna wrote:
> Just set:
> directive.if.tostring.nullcheck = false

We just went into production last night after testing in this
configuration for months, and we're getting this bizarre error message
that filled-up our logs files and the whole disk on the server.

Obviously, the root cause is at the bottom.:

INFO:  Velocity  [error] Could not initialize VelocityEngine -
org.apache.veloci
ty.exception.VelocityException: Velocimacro : Error using VM library :
/WEB-INF/
VM_global_library.vm
org.apache.velocity.exception.VelocityException: Velocimacro : Error
using VM li
brary : /WEB-INF/VM_global_library.vm
        at
org.apache.velocity.runtime.VelocimacroFactory.initVelocimacro(Veloci
macroFactory.java:219)
        at
org.apache.velocity.runtime.RuntimeInstance.init(RuntimeInstance.java
:274)
...
Caused by: org.apache.velocity.exception.VelocityException: Exception
thrown pro
cessing Template /WEB-INF/VM_global_library.vm
        at org.apache.velocity.Template.process(Template.java:164)
        at
org.apache.velocity.runtime.resource.ResourceManagerImpl.loadResource
(ResourceManagerImpl.java:437)
        at
org.apache.velocity.runtime.resource.ResourceManagerImpl.getResource(
ResourceManagerImpl.java:352)
        at
org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstan
ce.java:1533)
        at
org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstan
ce.java:1514)
        at
org.apache.velocity.runtime.VelocimacroFactory.initVelocimacro(Veloci
macroFactory.java:202)
        ... 28 more

Caused by: java.lang.ClassCastException:
'directive.if.tostring.nullcheck' doesn't map to a Boolean object
        at
org.apache.commons.collections.ExtendedProperties.getBoolean(Extended
Properties.java:1190)
        at
org.apache.commons.collections.ExtendedProperties.getBoolean(Extended
Properties.java:1157)
        at
org.apache.velocity.runtime.RuntimeInstance.getBoolean(RuntimeInstanc
e.java:1822)
        at
org.apache.velocity.runtime.parser.node.ASTReference.init(ASTReferenc
e.java:141)
        at
org.apache.velocity.runtime.parser.node.SimpleNode.init(SimpleNode.ja
va:309)
        at
org.apache.velocity.runtime.parser.node.ASTDirective.init(ASTDirectiv
e.java:95)
        at
org.apache.velocity.runtime.parser.node.SimpleNode.init(SimpleNode.ja
va:309)
        at org.apache.velocity.Template.initDocument(Template.java:227)
        at org.apache.velocity.Template.process(Template.java:135)
        ... 33 more

I'm going to temporarily remove this directive to get back up and
running, and I'll be looking at the code to see what's going on. If
anyone can make a suggestion in the meantime, I'm all ears.

I'm running Velocity 1.7 and Velocity Tools 2.0 with a few extra patches.

Thanks,
-chris


Re: Odd observation during debugging

Posted by Nathan Bubna <nb...@gmail.com>.
Just set:
directive.if.tostring.nullcheck = false

See VELOCITY-731 and VELOCITY-692 and others...

On Wed, Jul 11, 2012 at 9:13 AM, Christopher Schultz
<ch...@christopherschultz.net> wrote:
> All,
>
> I recently started getting intermittent OOMEs in my webapp that seem to
> be completely recoverable. The stack traces look like this:
>
> java.lang.OutOfMemoryError: Java heap space
>         at java.util.Arrays.copyOf(Arrays.java:2882)
>         at
> java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
>         at
> java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390
> )
>         at java.lang.StringBuilder.append(StringBuilder.java:119)
>         at java.lang.StringBuilder.append(StringBuilder.java:115)
>         at
> java.util.AbstractCollection.toString(AbstractCollection.java:422)
>         at
> org.apache.velocity.runtime.parser.node.ASTReference.evaluate(ASTReference.java:547)
>     [...]
>
> This happens so rarely, it's hard to catch, and it's fairly obvious what
> is happening: toString is being called on a really huge List. We try not
> to use really huge lists, but I suppose there is some potential danger
> in rare instances. I want to find those instances.
>
> Velocity 1.7, Tools 2.0 (plus some committed bugfixes that haven't made
> it into an official release, yet).
>
> So I thought I'd add some trickery to my VelocityContexts: We use our
> own subclasss of VelocityLayoutServlet for a number of things, and I
> have overridden the createContext() to wrap the Context the
> super.createContext() returns in a ChainedContext (btw, that's
> deprecated but doesn't document a replacement -- what should I use?). My
> wrapper wraps all List and Set objects in yet another set of wrappers
> which essentially pass-through everything except that their toString
> methods throw an exception: there should be no reason that a List or Set
> should have toString() called on it, because they should only be
> iterated over, for instance.
>
> But right on my login page, I see this exception after such instrumentation:
>
> SEVERE: Servlet.service() for servlet velocity threw exception
> org.apache.velocity.exception.VelocityException: Reference evaluation
> threw an exception at /layout/header.vm[line 18, column 5]
>         at
> org.apache.velocity.runtime.parser.node.ASTReference.evaluate(ASTRefe
> rence.java:551)
>         at
> org.apache.velocity.runtime.parser.node.ASTExpression.evaluate(ASTExp
> ression.java:62)
>         at
> org.apache.velocity.runtime.parser.node.ASTIfStatement.render(ASTIfSt
> atement.java:85)
>         at
> org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.
> java:342)
>         at
> org.apache.velocity.runtime.directive.Parse.render(Parse.java:260)
>         at
> org.apache.velocity.runtime.parser.node.ASTDirective.render(ASTDirect
> ive.java:207)
>         at
> org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.
> java:342)
>         at org.apache.velocity.Template.merge(Template.java:356)
>         [....]
> Caused by: java.lang.IllegalStateException: Request /myapp/login.vm,
> called toString on key 'extraStylesheets'
>         at
> com.chadis.web.servlet.VelocityLayoutServlet$WrappedList.toString(VelocityLayoutServlet.java:220)
>         at
> org.apache.velocity.runtime.parser.node.ASTReference.evaluate(ASTReference.java:547)
>
> If I look at everything that references the key 'extraStylesheets' in my
> login page (and the header, etc.), I get only these:
>
> login.vm only includes these references:
> #set($extraStylesheets = ['/includes/css/login.css'])
>
> I'm using VelocityLayoutServlet which is using my layouts/Default.vm (no
> references to 'extraStylesheets') which includes (via #parse)
> 'header.vm' which has this to say:
>
> #if($extraStylesheets)
>         #foreach($stylesheet in $extraStylesheets)
>     <link type="text/css" href="$link.setRelative($stylesheet)"
> rel="stylesheet" />
>         #end
> #end
>
> ...and that's it. Given the upper stack trace it seems that the #if() is
> evaluating the argument by converting it into a String for evaluation.
> Is that intentional? I always used "#if($foo)" to check to see if $foo
> had a value of any kind -- that is, if it was non-null (and non-false)
> but it appears that the value is being converted into a String for ...
> what purpose?
>
> I know a lot of changes have occurred over the years and that
> String-conversion is something that has seen some of that action. Is
> there a "better" way of determining if a List (or whatever) has a value?
> That is, without converting it to a String, first?
>
> One can argue that I shouldn't have Lists of objects so large that
> converting them to a String will cause an OOME, but the calling of
> toString on these Lists is certainly surprising.
>
> Thanks,
> -chris
>

---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscribe@velocity.apache.org
For additional commands, e-mail: user-help@velocity.apache.org