/*
* Copyright 2012 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.fmk.velocity;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solmix.api.call.DSCall;
import org.solmix.api.context.WebContext;
import org.solmix.api.datasource.DSRequest;
import org.solmix.api.datasource.DSRequestData;
import org.solmix.api.datasource.DataSource;
import org.solmix.api.exception.SlxException;
import org.solmix.api.types.Texception;
import org.solmix.api.types.Tmodule;
import org.solmix.commons.io.SlxFile;
import org.solmix.ds.context.Context;
import org.solmix.fmk.internal.DatasourceCM;
import org.solmix.fmk.util.DataTools;
/**
* Used Apache Velocity for template engine.dynamically constructed template.
* <p>
* <b>Use Velocity fundamental pattern.</b> <br>
* 1.Initialize Velocity. <br>
* 2.Create a context Object. <br>
* 3.Add your data object to the context.<br>
* 4.Choose a template.<br>
* 5.Merge the template and your data to product output.
* <p>
* Velocity can used Singleton Module or Separate Instance.{@link org.apache.velocity.app.Velocity} for singleton and
* {@link org.apache.velocity.app.VelocityEngine} for separate module
* <table border=1 >
* <tr width="100%" align="center" style="background-color : #BBFFFF">
* <td>Render template into output stream</td>
* </tr>
* <tr>
* <td>Once the runtime is initialized,you can do it with what you wish.Here is the method and description of what they
* do:<br>
* <li>{@link org.apache.velocity.app.VelocityEngine#evaluate(Context, Writer, String, String)},
* {@link org.apache.velocity.app.VelocityEngine#evaluate(org.apache.velocity.context.Context, java.io.Writer, String, java.io.InputStream)}
* These methods will render the input, in either the form of String or InputStream to an output Writer, using a Context
* that you provide. This is a very convenienient method to use for token replacement of strings, or if you keep
* 'templates' of VTL-containing content in a place like a database or other non-file storage, or simply generate such
* dynamically.<br>
* <li></td>
* </tr>
* <tr width="100%" align="center" style="background-color : #BBFFFF">
* <td>Velocity context</td>
* </tr>
* <tr>
* <td>the concept of the "Context" is central to Velocity, and is a common technique for moving a container of data
* around between parts of a system. The idea is that the context is a 'carrier' of data between the Java layer (or you
* the programmer) and the template layer ( or the designer ). You as the programmer will gather objects of various
* types, whatever your application calls for, and place them in the context. To the designer, these objects, and their
* methods and properties, will become accessable via template elements called references. Generally, you will work with
* the designer to determine the data needs for the application. In a sense, this will become an 'API' as you produce a
* data set for the designer to access in the template. Therefore, in this phase of the development process it is worth
* devoting some time and careful analysis.</td>
* </tr>
* </table>
*
* @author solmix.f@gmail.com
* @since 0.0.1
* @version 110035 2010-12-20 solmix-ds
*/
public class Velocity
{
public static final String RESPONSE_DATA = "responseData";
public static final String CRITERIA = "criteria";
public static final String VALUES = "values";
public static final String DSREQUEST = "dsRequest";
public static final String DATASOURCES = "dataSources";
public static final String UTIL = "util";
public static final String USER_ID = "userId";
public static final String HTTP_PARAMETERS = "httpParameters";
public static final String HTTP_ATTRIBUTES = "httpAttributes";
public static final String SESSION_ATTRIBUTES = "sessionAttributes";
public static final String SERVLET_REQUEST = "servletRequest";
public static final String TOOLS = "tools";
public static final String SESSION = "session";
public Velocity()
{
}
/**
* Get Velocity Engine.
* <p>
* <b>**Note**</b><br>
* Velocity Logger Configuration:<br>
* Velocity will automatically use either the Jakarta Avalon Logkit logger, or the Jakarta Log4j logger. It will do
* so by using whatever it finds in the current classpath, starting first with Logkit. If Logkit isn't found, it
* tries Log4j.
*
* @return
* @throws Exception
*/
public synchronized static VelocityEngine getEngine() {
if (vEngine != null) {
return vEngine;
} else {
vEngine = new VelocityEngine();
Properties properties = new Properties();
// For velocity resource loader.
String vtl = DatasourceCM.getProperties().getString(DatasourceCM.P_VELOCITY_TEMPLATE_DIR,"./");
properties.put("file.resource.loader.path",vtl);
properties.put("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.SimpleLog4JLogSystem");
properties.put("runtime.log.logsystem.log4j.category", "org.apache.Velocity");
vEngine.init(properties);
return vEngine;
}
}
public static Object evaluateTemplateFile(String fileName, Map<?,?> parameters) throws SlxException {
String path = DatasourceCM.getProperties().getString(DatasourceCM.P_VELOCITY_TEMPLATE_DIR);
try {
SlxFile file = new SlxFile((new StringBuilder()).append(path).append("/").append(fileName).toString());
return evaluate(file.getAsString(), parameters);
} catch (IOException e) {
throw new SlxException(Tmodule.VM,Texception.NO_FOUND,"Not found vm template file.",e);
}
}
public static String evaluateTemplateFileAsString(String fileName, Map<?,?> parameters) throws SlxException {
Object obj = evaluateTemplateFile(fileName, parameters);
return obj != null ? obj.toString() : null;
}
public static String evaluateAsString(String template, Map<?,?> parameters) throws SlxException {
Object obj = evaluate(template, parameters);
return obj != null ? obj.toString() : null;
}
public static Object evaluate(String template, Map<?,?> parameters) throws SlxException {
return evaluate(template, parameters, "notProvided", null, false);
}
public static String evaluateAsString(String template, Map<?,?> parameters, String operationName, DataSource ds, boolean quoteValues)
throws SlxException {
Object obj = evaluate(template, parameters, operationName, ds, quoteValues);
return obj != null ? obj.toString() : null;
}
public static Object evaluate(String template, Map<?,?> parameters, String operationName, DataSource ds, boolean quoteValues)
throws SlxException {
StringWriter out = new StringWriter();
VelocityContext context = new VelocityContext(parameters);
DSReferenceInsertionEventHandler handler = new DSReferenceInsertionEventHandler(context, ds, quoteValues);
try {
if (!getEngine().evaluate(context, out, operationName, template))
return null;
} catch (Exception e) {
throw new SlxException(Tmodule.DATASOURCE, Texception.VELOCITY_EVALUATE_EXCEPTION, "Velocity evalute exception:\n", e);
}
if (handler.foundObject != null && handler.foundObject.toString().equals(out.toString()))
return handler.foundObject;
else
return out.toString();
}
public static Boolean evaluateBooleanExpression(String template, Map<?,?> parameters) throws Exception {
return evaluateBooleanExpression(template, parameters, "notProvided", null);
}
public static Boolean evaluateBooleanExpression(String template, Map<?,?> parameters, String operationName, DataSource ds)
throws Exception {
Object obj = evaluate(template, parameters, operationName, ds, true);
if (obj == null || !(obj instanceof Boolean) && !obj.toString().trim().toLowerCase().equals("true")
&& !obj.toString().trim().toLowerCase().equals("false") && !obj.toString().trim().toLowerCase().equals("'true'")
&& !obj.toString().trim().toLowerCase().equals("'false'")) {
String postEval = obj.toString();
if (template.indexOf("'true'") != -1)
postEval = postEval.replaceAll("'true'", "true");
if (template.indexOf("'false'") != -1)
postEval = postEval.replaceAll("'false'", "false");
String wrappedTemplate = (new StringBuilder()).append("#if(").append(postEval).append(") true #else false #end").toString();
try {
obj = evaluate(wrappedTemplate, parameters, operationName, ds, true);
} catch (Exception e) {
log.error("evaluate " + wrappedTemplate + "occur error" + e.getMessage());
}
}
if (obj != null) {
if (obj instanceof Boolean)
return (Boolean) obj;
if (obj.toString().trim().toLowerCase().equals("true"))
return Boolean.TRUE;
if (obj.toString().trim().toLowerCase().equals("'true'"))
return Boolean.TRUE;
if (obj.toString().trim().toLowerCase().equals("false"))
return Boolean.FALSE;
if (obj.toString().trim().toLowerCase().equals("'false'"))
return Boolean.FALSE;
}
return null;
}
public static Map<String, Object> getStandardContextMap(DSRequest dsReq) {
DSRequestData _reqData = dsReq.getContext();
if (_reqData == null)
return new HashMap<String, Object>();
Map<String, Object> context;
if (dsReq.getDSCall() != null) {
context = new HashMap<String, Object>(dsReq.getDSCall().getTemplateContext());
context.put(RESPONSE_DATA, new ResponseDataHandler(dsReq.getDSCall(), dsReq));
} else {
context = new HashMap<String, Object>();
}
Map<String, Object> criteria = _reqData.getCriteria() == null ? (new HashMap<String, Object>()) : _reqData.getCriteria();
Map<String, Object> values = _reqData.getValues() == null ? new HashMap<String, Object>() : _reqData.getValues();
context.put(CRITERIA, criteria);
context.put(VALUES, values);
context.put(DSREQUEST, dsReq);
context.put(DATASOURCES, new DataSourcesHandler());
if (dsReq.getRequestContext() instanceof WebContext)
context.putAll(getServletContextMap((WebContext) dsReq.getRequestContext()));
context.put(UTIL, new Util());
context.put(TOOLS, new DataTools());
context.put(USER_ID, new UserIdHandler(dsReq));
if (_reqData.getTemplateContext() != null) {
for (Object key : _reqData.getTemplateContext().keySet()) {
if (context.get(key.toString()) != null)
log.warn((new StringBuilder()).append("DSRequest-specified a template context variable: ").append(key).append(
" collides with derived key of the same name").append(" - using the DSRequest-specified value.").toString());
context.put(key.toString(), _reqData.getTemplateContext().get(key));
}
}
return context;
}
public static Map<String, Object> getServletContextMap(DSCall dsc) {
if (dsc != null){
Context cxt=dsc.getRequestContext();
if(WebContext.class.isAssignableFrom(cxt.getClass())){
return getServletContextMap(WebContext.class.cast(cxt));
}
}
return new HashMap<String, Object>();
}
/**
* Convert {@link org.solmix.api.servlet.RequestContext} to stander velocity context
*
* @param reqContext
* @return
*/
public static Map<String, Object> getServletContextMap(WebContext reqContext) {
Map<String, Object> context = new HashMap<String, Object>();
ServletRequestAttributeMapFacade escRequest = null;
SessionAttributeMapFacade escSession = null;
if (reqContext != null) {
escRequest = new ServletRequestAttributeMapFacade(reqContext.getRequest());
if (reqContext.getRequest() != null)
escSession = new SessionAttributeMapFacade(reqContext.getRequest().getSession());
}
context.put(HTTP_PARAMETERS, new HttpParameterHandler(escRequest));
context.put(HTTP_ATTRIBUTES, new HttpAttributeHandler(escRequest));
context.put(SESSION_ATTRIBUTES, new HttpAttributeHandler(escSession));
if (escRequest != null)
context.put(SERVLET_REQUEST, escRequest);
if (escSession != null)
context.put(SESSION, escSession);
return context;
}
public static void evaluateTemplateFile(String vmFileName, Map<?,?> parameters,String encoding,Writer out) throws SlxException{
try {
VelocityContext context = new VelocityContext(parameters);
VelocityEngine engin= getEngine();
Template template;
if(encoding!=null)
template = engin.getTemplate(vmFileName, encoding);
else
template = engin.getTemplate(vmFileName);
template.merge(context, out);
} catch (ResourceNotFoundException e) {
throw new SlxException(Tmodule.VM,Texception.NO_FOUND,"Not found vm template file.",e);
} catch (ParseErrorException e) {
throw new SlxException(Tmodule.VM,Texception.PARSER_VM_FILE_EXCEPTION,"Exception When Paraser vm template file.",e);
}
}
private static Logger log = LoggerFactory.getLogger(Velocity.class.getName());
private static VelocityEngine vEngine;
}