diff --git a/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java b/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java index 58b7677..f1c9641 100644 --- a/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java +++ b/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java @@ -17,8 +17,11 @@ import java.io.StringReader; import java.io.StringWriter; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Properties; + import org.apache.ibatis.builder.BuilderException; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; @@ -27,15 +30,18 @@ public class VelocityFacade { + private static final String ADDITIONAL_CTX_ATTRIBUTES_KEY = "additional.context.attributes"; + private static final String EXTERNAL_PROPERTIES = "mybatis-velocity.properties"; + private static final RuntimeInstance engine; + + /** Contains thread safe objects to be set in the velocity context.*/ + private static final Map additionalCtxAttributes; + private static final Properties settings; static { - final Properties settings = new Properties(); - settings.setProperty("userdirective", - TrimDirective.class.getName() + "," + - WhereDirective.class.getName() + "," + - SetDirective.class.getName() + "," + - RepeatDirective.class.getName()); + settings = loadPropeties(); + additionalCtxAttributes = Collections.unmodifiableMap(loadAdditionalCtxAttributes()); engine = new RuntimeInstance(); engine.init(settings); } @@ -56,8 +62,50 @@ public static Object compile(String script, String name) { public static String apply(Object template, Map context) { final StringWriter out = new StringWriter(); - ((Template)template).merge(new VelocityContext(context), out); + context.putAll(additionalCtxAttributes); + ((Template) template).merge(new VelocityContext(context), out); return out.toString(); } + private static Properties loadPropeties() { + final Properties props = new Properties(); + // Defaults + props.setProperty("resource.loader", "class"); + props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + + + try { + // External properties + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + props.load(cl.getResourceAsStream(EXTERNAL_PROPERTIES)); + } catch (Exception ex) { + // No custom properties + } + + props.setProperty("userdirective", + TrimDirective.class.getName() + "," + + WhereDirective.class.getName() + "," + + SetDirective.class.getName() + "," + + RepeatDirective.class.getName()); + return props; + } + + private static Map loadAdditionalCtxAttributes() { + Map attributes = new HashMap(); + String additionalContextAttributes = settings.getProperty(ADDITIONAL_CTX_ATTRIBUTES_KEY); + if (additionalContextAttributes == null) { + return attributes; + } + + try { + String[] entries = additionalContextAttributes.split(","); + for (String str : entries) { + String[] entry = str.trim().split(":"); + attributes.put(entry[0].trim(), Class.forName(entry[1].trim()).newInstance()); + } + } catch (Exception ex) { + throw new BuilderException("Error parsing velocity property '" + ADDITIONAL_CTX_ATTRIBUTES_KEY + "'", ex); + } + return attributes; + } } diff --git a/src/test/java/mybatis-velocity.properties b/src/test/java/mybatis-velocity.properties new file mode 100644 index 0000000..9e9ebae --- /dev/null +++ b/src/test/java/mybatis-velocity.properties @@ -0,0 +1 @@ +additional.context.attributes=trailingWildCardFormatter:org.mybatis.scripting.velocity.use.TrailingWildCardFormatter \ No newline at end of file diff --git a/src/test/java/org/mybatis/scripting/velocity/use/TrailingWildCardFormatter.java b/src/test/java/org/mybatis/scripting/velocity/use/TrailingWildCardFormatter.java new file mode 100644 index 0000000..cb3cdc6 --- /dev/null +++ b/src/test/java/org/mybatis/scripting/velocity/use/TrailingWildCardFormatter.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012 MyBatis.org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.scripting.velocity.use; + +public class TrailingWildCardFormatter { + + public String formatLiteral(Object val) { + if (val == null) { + return "''"; + } + String param = val.toString().replaceAll("\'", "\''"); + return "'" + param + "%'"; + } + + public String format(Object val) { + if (val == null) { + return ""; + } + return val + "%"; + } + +} diff --git a/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java b/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java index 47127e4..4ec6b3b 100644 --- a/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java +++ b/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java @@ -129,6 +129,40 @@ public void testDynamicSelectWithExpressionParams() { } } + @Test + public void testSelectNamesWithFormattedParam() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + + Parameter p = new Parameter(true, "Fli"); + List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithFormattedParam", p); + assertEquals(3, answer.size()); + for (Name n : answer) { + assertEquals("Flintstone", n.getLastName()); + } + + } finally { + sqlSession.close(); + } + } + + @Test + public void testSelectNamesWithFormattedParamSafe() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + + Parameter p = new Parameter(true, "Fli"); + List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithFormattedParamSafe", p); + assertEquals(3, answer.size()); + for (Name n : answer) { + assertEquals("Flintstone", n.getLastName()); + } + + } finally { + sqlSession.close(); + } + } + @Test @SuppressWarnings("unchecked") public void testDynamicSelectWithIteration() { diff --git a/src/test/java/org/mybatis/scripting/velocity/use/mapper.xml b/src/test/java/org/mybatis/scripting/velocity/use/mapper.xml index b51d325..8bdd7f5 100644 --- a/src/test/java/org/mybatis/scripting/velocity/use/mapper.xml +++ b/src/test/java/org/mybatis/scripting/velocity/use/mapper.xml @@ -42,6 +42,19 @@ WHERE lastName LIKE @{pattern, javaType=string} + + + +