How could let MyBatis Generator overwriting the already generated *Mapper.xml? - maven

Like title, when i execute the mybatis-generator, i want to overwriting the already generated *Mapper.xml all, not merge!
but i try many config way, it doesn't implement correct.
and everytime is generator the more once the xml content.
like this:
<resultMap id="BaseResultMap" type="com.test.entity.GoodsEntity"> ...
<resultMap id="BaseResultMap" type="com.test.entity.GoodsEntity"> ...
<resultMap id="BaseResultMap" type="com.test.entity.GoodsEntity"> ...
in the properties, i had add this line:
<mybatis.generator.overwrite>true</mybatis.generator.overwrite>
and in the build > plugin, i add below lines:
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
<configurationFile>${mybatis.generator.configurationFile}</configurationFile>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>ob-maven-plugin-mybatis-generator</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</plugin>
in the mybatis-generator.xml, i try overwrite config yet.
all config it doesn't work goo.
How could I modify the configuration?

MyBatis generator will always merge XML files if it finds a match. There is currently no option to turn that off.

You can use plugin <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" /> in Mybatis Generator 1.3.7 to overwrite xml.
See http://www.mybatis.org/generator/reference/plugins.html
This plugin will disable the XML merge function for generated mapper XML files. This will cause the generator to respect the overwrite flag for XML files in the same way it does for Java files - if overwrite is true, then an existing file will be overwritten, else a new file will be written with a unique name.
This plugin can be helpful if you disable all comments.
Setting <property name="suppressAllComments" value="true" /> in Mybatis Generator configuration file can cause this problem. Mybatis Generator uses comments flag to decide whether to merge XML.
If you disable all comments, you might find the UnmergeableXmlMappersPlugin useful. It will cause the generator to respect the overwrite flag for XML files.
See http://www.mybatis.org/generator/configreference/commentGenerator.html

I was able to get around this by creating a plugin and adding it to the mybatis-generator-config.xml file. Note, of course, that this solution will cause the Mapper.xml files to always be overwritten regardless of whether or not the -overwrite flag is specified.
mybatis-generator-config.xml:
<generatorConfiguration>
...
<context id="myContextId">
<plugin type="com.mydomain.DeleteExistingSqlMapsPlugin"></plugin>
...
</context>
</generatorConfiguration>
DeleteExistingSqlMapsPlugin.java:
...
public class DeleteExistingSqlMapsPlugin extends PluginAdapter {
#Override
public boolean validate(List<String> warnings) {
return true;
}
#Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap,
IntrospectedTable introspectedTable)
{
String sqlMapPath = sqlMap.getTargetProject() + File.separator
+ sqlMap.getTargetPackage().replaceAll("\\.", File.separator)
+ File.separator + sqlMap.getFileName();
File sqlMapFile = new File(sqlMapPath);
sqlMapFile.delete();
return true;
}
}
This works because sqlMapGenerated() is called after a Mapper.xml file is created in memory but before it is written to disk.

I encountered the same problem today.To solve this problem, just need to change the version of mybatis-generator-maven-plugin。
<mybatis-generator-maven-plugin.version>1.3.4-SNAPSHOT</mybatis-generator-maven-plugin.version>

I write a plugin to merge xml mapper file.
And modifiy mybatis-generator-core to combine java and xml.
This can keep your xml and java file 's modification not override.
https://github.com/zwxbest/mybatis-generator-plugin
Usage:
<generatorConfiguration>
...
<context id="myContextId">
<plugin type="com.mydomain.CombineXmlPlugin"></plugin>
...
</context>
</generatorConfiguration>
Plugin Code:
package com.haitian.plugins;
import org.mybatis.generator.api.GeneratedXmlFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.ShellCallback;
import org.mybatis.generator.api.dom.xml.Element;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* User:zhangweixiao
* Description:
* old nodes is your existing xml file's first level nodes,like <insert><resultMap>
* new nodes is mybatis-generator generate for you to combine
* This compare the first level node's name and "id" attribute of new nodes and old nodes
* if the two equal,then new node will not generate
* so this can make you modification in old nodes not override.
* if you want to regenrate old node,delete it,it will generate new.
*/
public class CombineXmlPlugin extends PluginAdapter {
//shellCallback use TargetProject and TargetPackage to get targetFile
ShellCallback shellCallback = new DefaultShellCallback(false);
//save new nodes
org.mybatis.generator.api.dom.xml.Document document;
#Override
public boolean validate(List<String> warnings) {
return true;
}
/**
* assing document variable to get new nodes
* #param document
* #param introspectedTable
* #return
*/
#Override
public boolean sqlMapDocumentGenerated(org.mybatis.generator.api.dom.xml.Document document,
IntrospectedTable introspectedTable) {
this.document = document;
return true;
}
//new nodes is generated,but not write on disk,we just need to filter.
#Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap,
IntrospectedTable introspectedTable) {
try {
//get old nodes
File directory = shellCallback.getDirectory(sqlMap.getTargetProject(), sqlMap.getTargetPackage());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
File xmlFile = new File(directory, sqlMap.getFileName());
if (directory.exists() == false || xmlFile.exists() == false)
return true;
Document doc = db.parse(new FileInputStream(xmlFile));
org.w3c.dom.Element rootElement = doc.getDocumentElement();
NodeList list = rootElement.getChildNodes();
//get new nodes
List<Element> elements = document.getRootElement().getElements();
//get nodeName and the value of id attribute use regex
Pattern p = Pattern.compile("<(\\w+)\\s+id=\"(\\w+)\"");
boolean findSameNode = false;
// traverse new nodes to compare old nodes to filter
for (Iterator<Element> elementIt = elements.iterator(); elementIt.hasNext(); ) {
findSameNode = false;
String newNodeName = "";
String NewIdValue = "";
Element element = elementIt.next();
Matcher m = p.matcher(element.getFormattedContent(0));
if (m.find()) {
//get nodeName and the value of id attribute
newNodeName = m.group(1);
NewIdValue = m.group(2);
}
//if the nodeName of newNode and oldNode are equal
//and the id attribute of newNode and oldNode are equal
//then filter newNode
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
if (newNodeName.equals(node.getNodeName())) {
NamedNodeMap attr = node.getAttributes();
for (int j = 0; j < attr.getLength(); j++) {
Node attrNode = attr.item(j);
if (attrNode.getNodeName().equals("id") && attrNode.getNodeValue().equals(NewIdValue)) {
//filter new node,just delete it ,and it will not generate
elementIt.remove();
findSameNode = true;
break;
}
}
if (findSameNode == true)
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
you add "batchInsert",and delte insertSelective,and modify other node.
then regenerate xml mapper file,only insertSelective will be generated,others will not be override.

Related

value annotation with wildcards for file name starting with string in spring boot

I have below value in my application.properties file
input.file-path=c:\\temp\\data
In that path I have two files:
Students_Input_20221212.dat
Workers_Input_20221212.dat
is there a way using #Value annotation to get each of the files by using the start with string? Something like below:
#Value("${input.file-path}/Students_Input_*.dat")
private String inputFileStudents;
#Value("${input.file-path}/Workers_Input_*.dat")
private String inputFileWorkers;
So I have:
inputFileStudents = "c:\\temp\\data\\Students_Input_20221212.dat"
inputFileWorkers = "c:\\temp\\data\\Workers_Input_20221212.dat"
Finally found:
[PathMatching]ResourcePatternResolver!
Consider:
spring-boot:3 starter (web)
properties:
# existing path (win):
input.file=C:\\temp\\data
# file filter (ant path matcher):
input.students.filter=Students_Input_*.dat
input.workers.filter=Workers_Input_*.dat
Testing time:
// ...
import java.util.Arrays;
// import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.support.ResourcePatternResolver; // !!
// ...
#Bean
InitializingBean patternResolverShowCase( /* <- just a demo ... */
/*#Autowired*/ ResourcePatternResolver resolver, /* <- autowire/#value "anywhere"(in spring beans)... */
#Value("${input.file}") String folder, /* <- alternative for #Value: (type-safe) #ConfigurationProperties ;) */
#Value("${input.students.filter}") String studentsFilter, /* < filter1 */
#Value("${input.workers.filter}") String workersFilter /* < filter2 */
) {
return () -> { // <- initializingBean - lambda..
System.err.println( // <- just a demo...
Arrays.toString( // ..do whatever you like with:
resolver.getResources("file:" + folder + "/" + studentsFilter)
)
);
System.err.println(
Arrays.toString( // ... :
resolver.getResources("file:" + folder + "/" + workersFilter)
)
);
};
}
With SpEL
Declaring (assigning name and implementation class, otherwise we get a "nameless default"):
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; // !
// ...
#Bean
ResourcePatternResolver rssResolver() {
return new PathMatchingResourcePatternResolver();
}
..we can also just (SpEL):
import org.springframework.core.io.Resource; // !
// ...
#Value("#{#rssResolver.getResources('file:'+'${input.file}'+'/'+'${input.students.filter}')}") Resource[] studentFiles;
#Value("#{#rssResolver.getResources('file:'+'${input.file}'+'/'+'${input.workers.filter}')}") Resource[] workerFiles;
// do whatever you like with'em (in spring) ...
Explanations
above link...
properties, path separator handling and adjustment: to your needs/suits.
#Value("#{...}") SpEL expression (in a #Value ...).
#rssResolver refers to "rssResolver" bean.
plus:
Original answer:
Wildcards will be hard (from #Value), but this (wiring a
directory into bean context) is easy-peasy:
Properties:
# existing path (win):
myFolder=C:\\temp\\data
# file filter (regex!):
myFileFilter=^test.*\.txt$
Test
#Bean InitializingBean fileResourceShowCase(/* <- just a demo */
#Value("${myFolder}") FileSystemResource rss1, /* <- org.springframework.core.io */
#Value("#{T(java.nio.file.Path).of('${myFolder}')}") Path path, /* <- java.nio */
#Value("#{new java.io.File('${myFolder}')}") File file, /* <- java.io */
#Value("${myFileFilter}") String filter /* <- custom/extra */
) {
return () -> { // 3 ways to access java.io.File:
System.err.println(rss1.getFile());
System.err.println(path.toFile());
System.err.println(file);
// apply the filter:
File[] files = file.listFiles((f) -> f.getName().matches(filter));
for (File f : files) {
System.err.println(f);
}
};
}
Explanations:
#Value("${myFolder}") FileSystemResource rss1: access the folder as a (spring) FileSystemResource just by it's name (represented by
${myFolder} placeholder). [prefer!]
#Value("#{T(java.nio.file.Path).of('${myFolder}')}") Path path: SpEL expression for (static) Path.of(myFolder) (where ${myFolder}
is resolved as above).
#Value("#{new java.io.File('${myFolder}')}") File file: SpEL expression for: new File(myFolder) ...
Links:
(spring)Resources
SpEL
Don'ts
Unfortunately we cannot do this with SpEL:
#Value("""
#{
T(java.nio.file.Path).of('${myFolder}')
.toFile()
.listFiles(
(f)->f.getName().matches('${myFileFilter}')
)
}
""") File[] files; // -> EL1042E: Problem parsing right operand ;(;(
see: Why doesn't this stream & lambda expression work with SpEL declaration?
Neither this:
#Value("${myFolder}/**") FileSystemResource[] rssArr // -> java.nio.file.InvalidPathException: Illegal char <*> at index ...
... the last (#Value("${myFolder}/**")) approach brought me to: https://www.google.com/search?q=spring+antpathmatcher+filesystem ;)

Is it possible to add variables in config.xml?

I have following xml:
<config>
<global>
<resources>
<dbresource>
<connection>
<host><![CDATA[localhost]]></host>
<username><![CDATA[root]]></username>
<password><![CDATA[1234]]></password>
<dbname><![CDATA[db_test]]></dbname>
</connection>
</dbresource>
...
...
I want to add all these Informations like(host, username, password and dbname) in admin_core_data and read them to connect to an external database. I know how to read/write data into admin_core_data using system.xml.
and I can connect to external database with config.xml but I need to let the employee to change it easily in "admin/system/configuration" because they cant edit xml files.
or any other idea guys, what to do in this situation?
Any advice guys?
Thank you..
There is slight different approach by which you can implement the functionality. Add a new system.xml and then need to update the database configuration in madel files.
const XML_CONFIG_EDB_HOST = 'edb_settings/dbconnection/host';
const XML_CONFIG_EDB_DBNAME = 'edb_settings/dbconnection/dbname';
const XML_CONFIG_EDB_USERNAME = 'edb_settings/dbconnection/username';
const XML_CONFIG_EDB_PASSWORD = 'edb_settings/dbconnection/password';
const EXTERNAL_RESOURCE_NAME = 'edb_connection';
/* #var array */
protected $_config;
/* #var Varien_Db_Adapter_Pdo_Mysql */
protected $_connection;
public function __construct() {
$this->_setConnection();
}
/**
* Gets the Config Settings in the Admin Settings for DB Connection
* #return array
*/
public function getConfig() {
if (!$this->_config) {
$this->_config = array();
// Setting the Values from the Admin Config
$this->_config['host'] = Mage::getStoreConfig(self::XML_CONFIG_EDB_HOST);
$this->_config['dbname'] = Mage::getStoreConfig(self::XML_CONFIG_EDB_DBNAME);
$this->_config['username'] = Mage::getStoreConfig(self::XML_CONFIG_EDB_USERNAME);
$this->_config['password'] = Mage::getStoreConfig(self::XML_CONFIG_EDB_PASSWORD);
// Setting the default Values
$this->_config['initStatements'] = 'SET NAMES utf8';
$this->_config['model'] = 'mysql4';
$this->_config['type'] = 'pdo_mysql';
$this->_config['pdoType'] = '';
$this->_config['active'] = '1';
}
return $this->_config;
}
/**
* Gets External DB Connection Resource
*
* #return Varien_Db_Adapter_Pdo_Mysql
*/
public function getConnection() {
if (!$this->_connection) {
$this->_setConnection();
}
return $this->_connection;
}
private function _setConnection() {
if (!$this->_connection) {
$this->_connection = Mage::getSingleton('core/resource')->createConnection(self::EXTERNAL_RESOURCE_NAME, 'pdo_mysql', $this->getConfig());
}
}
public static function getConnectionName() {
return self::EXTERNAL_RESOURCE_NAME;
}
You can take a reference from below link
External database configuration
You could use Magento standard way of getting store config.
$configValue = Mage::getStoreConfig('sectionName/groupName/fieldName');
sectionName, groupName and fieldName should be defined in etc/system.xml file of your module.
The above code will automatically fetch config value of currently viewed store.
Check some system.xml files to see how it is defined in Magento, so you can easily add it to your module.

Include YAML files from snakeyaml

I would like to have YAML files with an include, similar to this question, but with Snakeyaml:
How can I include an YAML file inside another?
For example:
%YAML 1.2
---
!include "load.yml"
!include "load2.yml"
I am having a lot of trouble with it. I have the Constructor defined, and I can make it import one document, but not two. The error I get is:
Exception in thread "main" expected '<document start>', but found Tag
in 'reader', line 5, column 1:
!include "load2.yml"
^
With one include, Snakeyaml is happy that it finds an EOF and processes the import. With two, it's not happy (above).
My java source is:
package yaml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.AbstractConstruct;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.Tag;
public class Main {
final static Constructor constructor = new MyConstructor();
private static class ImportConstruct extends AbstractConstruct {
#Override
public Object construct(Node node) {
if (!(node instanceof ScalarNode)) {
throw new IllegalArgumentException("Non-scalar !import: " + node.toString());
}
final ScalarNode scalarNode = (ScalarNode)node;
final String value = scalarNode.getValue();
File file = new File("src/imports/" + value);
if (!file.exists()) {
return null;
}
try {
final InputStream input = new FileInputStream(new File("src/imports/" + value));
final Yaml yaml = new Yaml(constructor);
return yaml.loadAll(input);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
}
private static class MyConstructor extends Constructor {
public MyConstructor() {
yamlConstructors.put(new Tag("!include"), new ImportConstruct());
}
}
public static void main(String[] args) {
try {
final InputStream input = new FileInputStream(new File("src/imports/example.yml"));
final Yaml yaml = new Yaml(constructor);
Object object = yaml.load(input);
System.out.println("Loaded");
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
finally {
}
}
}
Question is, has anybody done a similar thing with Snakeyaml? Any thoughts as to what I might be doing wrong?
I see two issues:
final InputStream input = new FileInputStream(new File("src/imports/" + value));
final Yaml yaml = new Yaml(constructor);
return yaml.loadAll(input);
You should be using yaml.load(input), not yaml.loadAll(input). The loadAll() method returns multiple objects, but the construct() method expects to return a single object.
The other issue is that you may have some inconsistent expectations with the way that the YAML processing pipeline works:
If you think that your !include works like in C where the preprocessor sticks in the contents of the included file, the way to implement it would be to handle it in the Presentation stage (parsing) or Serialization stage (composing). But you have implemented it in the Representation stage (constructing), so !include returns an object, and the structure of your YAML file must be consistent with this.
Let's say that you have the following files:
test1a.yaml
activity: "herding cats"
test1b.yaml
33
test1.yaml
favorites: !include test1a.yaml
age: !include test1b.yaml
This would work ok, and would be equivalent to
favorites:
activity: "herding cats"
age: 33
But the following file would not work:
!include test1a.yaml
!include test1b.yaml
because there is nothing to say how to combine the two values in a larger hierarchy. You'd need to do this, if you want an array:
- !include test1a.yaml
- !include test1b.yaml
or, again, handle this custom logic in an earlier stage such as parsing or composing.
Alternatively, you need to tell the YAML library that you are starting a 2nd document (which is what the error is complaining about: expected '<document start>') since YAML supports multiple "documents" (top-level values) in a single .yaml file.

How to filter a TreeGrid?

I currently have a TreeGrid which shows nodes with names. The data is coming from a manually populated DataSource.
When setting the filter on the nodeName field, The filter is not done recursevily and thus I can only filter the Root node.
How can I tell the filter to search for matches in child nodes?
PS: in the code below, I have 3 nodes Root > Run > Child1. If i try the filter and type "R", I get Root and Run. But if i Type "C", I get "no results found"
Code
DataSource:
package murex.otk.gwt.gui.client.ui.datasource;
import java.util.List;
import murex.otk.gwt.gui.client.ui.record.TreeRecord;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.fields.DataSourceIntegerField;
import com.smartgwt.client.data.fields.DataSourceTextField;
public class ClassesDataSource extends DataSource {
private static ClassesDataSource instance = null;
public static ClassesDataSource getInstance() {
if (instance == null) {
instance = new ClassesDataSource("classesDataSource");
}
return instance;
}
private ClassesDataSource(String id) {
setID(id);
DataSourceTextField nodeNameField = new DataSourceTextField("nodeName");
nodeNameField.setCanFilter(true);
nodeNameField.setRequired(true);
DataSourceIntegerField nodeIdField = new DataSourceIntegerField("nodeId");
nodeIdField.setPrimaryKey(true);
nodeIdField.setRequired(true);
DataSourceIntegerField nodeParentField = new DataSourceIntegerField("nodeParent");
nodeParentField.setRequired(true);
nodeParentField.setForeignKey(id + ".nodeId");
nodeParentField.setRootValue(0);
setFields(nodeIdField, nodeNameField, nodeParentField);
setClientOnly(true);
}
public void populateDataSource(List<String> classNames) {
TreeRecord root = new TreeRecord("Root", 0);
addData(root);
TreeRecord child1 = new TreeRecord("Child1", root.getNodeId());
addData(child1);
TreeRecord child2 = new TreeRecord("Run", child1.getNodeId());
addData(child2);
}
}
Main
public void onModuleLoad() {
ClassesDataSource.getInstance().populateDataSource(new ArrayList<String>());
final TreeGrid employeeTree = new TreeGrid();
employeeTree.setHeight(350);
employeeTree.setDataSource(ClassesDataSource.getInstance());
employeeTree.setAutoFetchData(true);
TreeGridField field = new TreeGridField("nodeName");
field.setCanFilter(true);
employeeTree.setFields(field);
employeeTree.setShowFilterEditor(true);
employeeTree.setAutoFetchAsFilter(true);
employeeTree.setFilterOnKeypress(true);
employeeTree.draw();
}
I solved this.
The problem was that the filter was calling the server to fetch data whereas my datasource was set to Client Only. To fix this, the employeeTree must have employeeTree.setLoadDataOnDemand(false);
You may also use employeeTree.setKeepParentsOnFilter(true)
http://www.smartclient.com/smartgwtee/javadoc/com/smartgwt/client/widgets/tree/TreeGrid.html#setKeepParentsOnFilter(java.lang.Boolean)

In freemarker is it possible to check to see if a file exists before including it?

We are trying to build a system in freemarker where extension files can be optionally added to replace blocks of the standard template.
We have gotten to this point
<#attempt>
<#include "extension.ftl">
<#recover>
Standard output
</#attempt>
So - if the extension.ftl file exists it will be used otherwise the part inside of the recover block is output.
The problem with this is that freemarker always logs the error that caused the recover block to trigger.
So we need one of two things:
Don't call the include if the file doesn't exist - thus the need to check for file existence.
-OR-
A way to prevent the logging of the error inside the recover block without changing the logging to prevent ALL freemarker errors from showing up.
easier solution would be:
<#attempt>
<#import xyz.ftl>
your_code_here
<#recover>
</#attempt>
We've written a custom macro which solves this for us. In early testing, it works well. To include it, add something like this (where mm is a Spring ModelMap):
mm.addAttribute(IncludeIfExistsMacro.MACRO_NAME, new IncludeIfExistsMacro());
import java.io.IOException;
import java.util.Map;
import org.apache.commons.io.FilenameUtils;
import freemarker.cache.TemplateCache;
import freemarker.cache.TemplateLoader;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
/**
* This macro optionally includes the template given by path. If the template isn't found, it doesn't
* throw an exception; instead it renders the nested content (if any).
*
* For example:
* <#include_if_exists path="module/${behavior}.ftl">
* <#include "default.ftl">
* </#include_if_exists>
*
* #param path the path of the template to be included if it exists
* #param nested (optional) body could be include directive or any other block of code
*/
public class IncludeIfExistsMacro implements TemplateDirectiveModel {
private static final String PATH_PARAM = "path";
public static final String MACRO_NAME = "include_if_exists";
#Override
public void execute(Environment environment, Map map, TemplateModel[] templateModel,
TemplateDirectiveBody directiveBody) throws TemplateException, IOException {
if (! map.containsKey(PATH_PARAM)) {
throw new RuntimeException("missing required parameter '" + PATH_PARAM + "' for macro " + MACRO_NAME);
}
// get the current template's parent directory to use when searching for relative paths
final String currentTemplateName = environment.getTemplate().getName();
final String currentTemplateDir = FilenameUtils.getPath(currentTemplateName);
// look up the path relative to the current working directory (this also works for absolute paths)
final String path = map.get(PATH_PARAM).toString();
final String fullTemplatePath = TemplateCache.getFullTemplatePath(environment, currentTemplateDir, path);
TemplateLoader templateLoader = environment.getConfiguration().getTemplateLoader();
if (templateLoader.findTemplateSource(fullTemplatePath) != null) {
// include the template for the path, if it's found
environment.include(environment.getTemplateForInclusion(fullTemplatePath, null, true));
} else {
// otherwise render the nested content, if there is any
if (directiveBody != null) {
directiveBody.render(environment.getOut());
}
}
}
}
I had this exact need as well but I didn't want to use FreeMarker's ObjectConstructor (it felt too much like a scriptlet for my taste).
I wrote a custom FileTemplateLoader:
public class CustomFileTemplateLoader
extends FileTemplateLoader {
private static final String STUB_FTL = "/tools/empty.ftl";
public CustomFileTemplateLoader(File baseDir) throws IOException {
super(baseDir);
}
#Override
public Object findTemplateSource(String name) throws IOException {
Object result = null;
if (name.startsWith("optional:")) {
result = super.findTemplateSource(name.replace("optional:", ""));
if (result == null) {
result = super.findTemplateSource(STUB_FTL);
}
}
if (result == null) {
result = super.findTemplateSource(name);
}
return result;
}
}
And my corresponding FreeMarker macro:
<#macro optional_include name>
<#include "/optional:" + name>
</#macro>
An empty FTL file was required (/tools/empty.ftl) which just contains a comment explaining its existence.
The result is that an "optional" include will just include this empty FTL if the requested FTL cannot be found.
You can use also use Java method to check file exist or not.
Java Method-
public static boolean checkFileExistance(String filePath){
File tmpDir = new File(filePath);
boolean exists = tmpDir.exists();
return exists;
}
Freemarker Code-
<#assign fileExists = (Static["ClassName"].checkFileExistance("Filename"))?c/>
<#if fileExists = "true">
<#include "/home/demo.ftl"/>
<#else>
<#include "/home/index.ftl">
</#if>
Try this to get the base path:
<#assign objectConstructor = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign file = objectConstructor("java.io.File","")>
<#assign path = file.getAbsolutePath()>
<script type="text/javascript">
alert("${path?string}");
</script>
Then this to walk the directory structure:
<#assign objectConstructor = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign file = objectConstructor("java.io.File","target/test.ftl")>
<#assign exist = file.exists()>
<script type="text/javascript">
alert("${exist?string}");
</script>

Resources