1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.executor.loader;
17
18 import java.io.Serializable;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.security.AccessController;
22 import java.security.PrivilegedActionException;
23 import java.security.PrivilegedExceptionAction;
24 import java.sql.SQLException;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Set;
30
31 import org.apache.ibatis.cursor.Cursor;
32 import org.apache.ibatis.executor.BaseExecutor;
33 import org.apache.ibatis.executor.BatchResult;
34 import org.apache.ibatis.executor.ExecutorException;
35 import org.apache.ibatis.logging.Log;
36 import org.apache.ibatis.logging.LogFactory;
37 import org.apache.ibatis.mapping.BoundSql;
38 import org.apache.ibatis.mapping.MappedStatement;
39 import org.apache.ibatis.reflection.MetaObject;
40 import org.apache.ibatis.session.Configuration;
41 import org.apache.ibatis.session.ResultHandler;
42 import org.apache.ibatis.session.RowBounds;
43
44
45
46
47
48 public class ResultLoaderMap {
49
50 private final Map<String, LoadPair> loaderMap = new HashMap<>();
51
52 public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
53 String upperFirst = getUppercaseFirstProperty(property);
54 if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
55 throw new ExecutorException("Nested lazy loaded result property '" + property + "' for query id '"
56 + resultLoader.mappedStatement.getId()
57 + " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
58 }
59 loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
60 }
61
62 public final Map<String, LoadPair> getProperties() {
63 return new HashMap<>(this.loaderMap);
64 }
65
66 public Set<String> getPropertyNames() {
67 return loaderMap.keySet();
68 }
69
70 public int size() {
71 return loaderMap.size();
72 }
73
74 public boolean hasLoader(String property) {
75 return loaderMap.containsKey(property.toUpperCase(Locale.ENGLISH));
76 }
77
78 public boolean load(String property) throws SQLException {
79 LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
80 if (pair != null) {
81 pair.load();
82 return true;
83 }
84 return false;
85 }
86
87 public void remove(String property) {
88 loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
89 }
90
91 public void loadAll() throws SQLException {
92 final Set<String> methodNameSet = loaderMap.keySet();
93 String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
94 for (String methodName : methodNames) {
95 load(methodName);
96 }
97 }
98
99 private static String getUppercaseFirstProperty(String property) {
100 String[] parts = property.split("\\.");
101 return parts[0].toUpperCase(Locale.ENGLISH);
102 }
103
104
105
106
107 public static class LoadPair implements Serializable {
108
109 private static final long serialVersionUID = 20130412;
110
111
112
113 private static final String FACTORY_METHOD = "getConfiguration";
114
115
116
117 private final transient Object serializationCheck = new Object();
118
119
120
121 private transient MetaObject metaResultObject;
122
123
124
125 private transient ResultLoader resultLoader;
126
127
128
129 private transient Log log;
130
131
132
133 private Class<?> configurationFactory;
134
135
136
137 private final String property;
138
139
140
141 private String mappedStatement;
142
143
144
145 private Serializable mappedParameter;
146
147 private LoadPair(final String property, MetaObject metaResultObject, ResultLoader resultLoader) {
148 this.property = property;
149 this.metaResultObject = metaResultObject;
150 this.resultLoader = resultLoader;
151
152
153 if (metaResultObject != null && metaResultObject.getOriginalObject() instanceof Serializable) {
154 final Object mappedStatementParameter = resultLoader.parameterObject;
155
156
157 if (mappedStatementParameter instanceof Serializable) {
158 this.mappedStatement = resultLoader.mappedStatement.getId();
159 this.mappedParameter = (Serializable) mappedStatementParameter;
160
161 this.configurationFactory = resultLoader.configuration.getConfigurationFactory();
162 } else {
163 Log log = this.getLogger();
164 if (log.isDebugEnabled()) {
165 log.debug("Property [" + this.property + "] of [" + metaResultObject.getOriginalObject().getClass()
166 + "] cannot be loaded " + "after deserialization. Make sure it's loaded before serializing "
167 + "forenamed object.");
168 }
169 }
170 }
171 }
172
173 public void load() throws SQLException {
174
175
176
177
178 if (this.metaResultObject == null) {
179 throw new IllegalArgumentException("metaResultObject is null");
180 }
181 if (this.resultLoader == null) {
182 throw new IllegalArgumentException("resultLoader is null");
183 }
184
185 this.load(null);
186 }
187
188 public void load(final Object userObject) throws SQLException {
189 if (this.metaResultObject == null || this.resultLoader == null) {
190 if (this.mappedParameter == null) {
191 throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
192 + "required parameter of mapped statement [" + this.mappedStatement + "] is not serializable.");
193 }
194
195 final Configuration config = this.getConfiguration();
196 final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
197 if (ms == null) {
198 throw new ExecutorException(
199 "Cannot lazy load property [" + this.property + "] of deserialized object [" + userObject.getClass()
200 + "] because configuration does not contain statement [" + this.mappedStatement + "]");
201 }
202
203 this.metaResultObject = config.newMetaObject(userObject);
204 this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
205 metaResultObject.getSetterType(this.property), null, null);
206 }
207
208
209
210
211
212 if (this.serializationCheck == null) {
213 final ResultLoader old = this.resultLoader;
214 this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
215 old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
216 }
217
218 this.metaResultObject.setValue(property, this.resultLoader.loadResult());
219 }
220
221 private Configuration getConfiguration() {
222 if (this.configurationFactory == null) {
223 throw new ExecutorException("Cannot get Configuration as configuration factory was not set.");
224 }
225
226 Object configurationObject;
227 try {
228 final Method factoryMethod = this.configurationFactory.getDeclaredMethod(FACTORY_METHOD);
229 if (!Modifier.isStatic(factoryMethod.getModifiers())) {
230 throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
231 + FACTORY_METHOD + "] is not static.");
232 }
233
234 if (!factoryMethod.isAccessible()) {
235 configurationObject = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
236 try {
237 factoryMethod.setAccessible(true);
238 return factoryMethod.invoke(null);
239 } finally {
240 factoryMethod.setAccessible(false);
241 }
242 });
243 } else {
244 configurationObject = factoryMethod.invoke(null);
245 }
246 } catch (final ExecutorException ex) {
247 throw ex;
248 } catch (final NoSuchMethodException ex) {
249 throw new ExecutorException("Cannot get Configuration as factory class [" + this.configurationFactory
250 + "] is missing factory method of name [" + FACTORY_METHOD + "].", ex);
251 } catch (final PrivilegedActionException ex) {
252 throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
253 + FACTORY_METHOD + "] threw an exception.", ex.getCause());
254 } catch (final Exception ex) {
255 throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
256 + FACTORY_METHOD + "] threw an exception.", ex);
257 }
258
259 if (!(configurationObject instanceof Configuration)) {
260 throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
261 + FACTORY_METHOD + "] didn't return [" + Configuration.class + "] but ["
262 + (configurationObject == null ? "null" : configurationObject.getClass()) + "].");
263 }
264
265 return Configuration.class.cast(configurationObject);
266 }
267
268 private Log getLogger() {
269 if (this.log == null) {
270 this.log = LogFactory.getLog(this.getClass());
271 }
272 return this.log;
273 }
274 }
275
276 private static final class ClosedExecutor extends BaseExecutor {
277
278 public ClosedExecutor() {
279 super(null, null);
280 }
281
282 @Override
283 public boolean isClosed() {
284 return true;
285 }
286
287 @Override
288 protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
289 throw new UnsupportedOperationException("Not supported.");
290 }
291
292 @Override
293 protected List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
294 throw new UnsupportedOperationException("Not supported.");
295 }
296
297 @Override
298 protected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
299 ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
300 throw new UnsupportedOperationException("Not supported.");
301 }
302
303 @Override
304 protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
305 throws SQLException {
306 throw new UnsupportedOperationException("Not supported.");
307 }
308 }
309 }