1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.scripting.freemarker.support;
17
18 import java.io.IOException;
19 import java.lang.reflect.Method;
20 import java.util.Optional;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ConcurrentMap;
23
24 import org.apache.ibatis.builder.annotation.ProviderContext;
25 import org.apache.ibatis.io.Resources;
26 import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver;
27 import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig;
28 import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig.TemplateFileConfig.PathProviderConfig;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public class TemplateFilePathProvider {
74
75 private static final PathGenerator DEFAULT_PATH_GENERATOR = TemplateFilePathProvider::generateTemplatePath;
76 private static final FreeMarkerLanguageDriverConfig DEFAULT_LANGUAGE_DRIVER_CONFIG = FreeMarkerLanguageDriverConfig
77 .newInstance();
78
79 private static PathGenerator pathGenerator = DEFAULT_PATH_GENERATOR;
80 private static FreeMarkerLanguageDriverConfig languageDriverConfig = DEFAULT_LANGUAGE_DRIVER_CONFIG;
81
82 private static ConcurrentMap<ProviderContext, String> cache = new ConcurrentHashMap<>();
83
84 private TemplateFilePathProvider() {
85
86 }
87
88
89
90
91
92
93
94 public static void setCustomTemplateFilePathGenerator(PathGenerator pathGenerator) {
95 TemplateFilePathProvider.pathGenerator = Optional.ofNullable(pathGenerator).orElse(DEFAULT_PATH_GENERATOR);
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109 public static void setLanguageDriverConfig(FreeMarkerLanguageDriverConfig languageDriverConfig) {
110 TemplateFilePathProvider.languageDriverConfig = Optional.ofNullable(languageDriverConfig)
111 .orElse(DEFAULT_LANGUAGE_DRIVER_CONFIG);
112 }
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 @SuppressWarnings("unused")
134 public static String provideSql(ProviderContext context) {
135 return languageDriverConfig.getTemplateFile().getPathProvider().isCacheEnabled()
136 ? cache.computeIfAbsent(context, c -> providePath(c.getMapperType(), c.getMapperMethod(), c.getDatabaseId()))
137 : providePath(context.getMapperType(), context.getMapperMethod(), context.getDatabaseId());
138 }
139
140
141
142
143 public static void clearCache() {
144 cache.clear();
145 }
146
147 static String providePath(Class<?> mapperType, Method mapperMethod, String databaseId) {
148 boolean fallbackDeclaringClass = mapperType != mapperMethod.getDeclaringClass();
149 boolean fallbackDatabase = databaseId != null;
150 String path = pathGenerator.generatePath(mapperType, mapperMethod, databaseId);
151 if (exists(path)) {
152 return path;
153 }
154 if (fallbackDatabase) {
155 path = pathGenerator.generatePath(mapperType, mapperMethod, null);
156 if (exists(path)) {
157 return path;
158 }
159 }
160 if (fallbackDeclaringClass) {
161 path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, databaseId);
162 if (exists(path)) {
163 return path;
164 }
165 if (fallbackDatabase) {
166 path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, null);
167 if (exists(path)) {
168 return path;
169 }
170 }
171 }
172 throw new IllegalStateException("The SQL template file not found. mapperType:[" + mapperType + "] mapperMethod:["
173 + mapperMethod + "] databaseId:[" + databaseId + "]");
174 }
175
176 private static String generateTemplatePath(Class<?> type, Method method, String databaseId) {
177 Package pkg = type.getPackage();
178 String packageName = pkg == null ? "" : pkg.getName();
179 String className = type.getName().substring(packageName.length() + (packageName.isEmpty() ? 0 : 1));
180
181 PathProviderConfig pathProviderConfig = languageDriverConfig.getTemplateFile().getPathProvider();
182 StringBuilder path = new StringBuilder();
183 if (!pathProviderConfig.getPrefix().isEmpty()) {
184 path.append(pathProviderConfig.getPrefix());
185 }
186 if (pathProviderConfig.isIncludesPackagePath() && !packageName.isEmpty()) {
187 path.append(packageName.replace('.', '/')).append('/');
188 }
189 path.append(className);
190 if (pathProviderConfig.isSeparateDirectoryPerMapper()) {
191 path.append('/');
192 if (pathProviderConfig.isIncludesMapperNameWhenSeparateDirectory()) {
193 path.append(className).append('-');
194 }
195 } else {
196 path.append('-');
197 }
198 path.append(method.getName());
199 if (databaseId != null) {
200 path.append('-').append(databaseId);
201 }
202 path.append(".ftl");
203 return path.toString();
204 }
205
206 private static boolean exists(String path) {
207 String basePath = languageDriverConfig.getTemplateFile().getBaseDir();
208 String actualPath = basePath.isEmpty() ? path : basePath + (basePath.endsWith("/") ? "" : "/") + path;
209 try {
210 Resources.getResourceURL(actualPath);
211 return true;
212 } catch (IOException e) {
213 return false;
214 }
215 }
216
217
218
219
220 @FunctionalInterface
221 public interface PathGenerator {
222
223
224
225
226
227
228
229
230
231
232
233
234
235 String generatePath(Class<?> type, Method method, String databaseId);
236
237 }
238
239 }