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
56 + "' for query id '" + 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 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 ["
166 + metaResultObject.getOriginalObject().getClass() + "] cannot be loaded "
167 + "after deserialization. Make sure it's loaded before serializing "
168 + "forenamed object.");
169 }
170 }
171 }
172 }
173
174 public void load() throws SQLException {
175
176
177 if (this.metaResultObject == null) {
178 throw new IllegalArgumentException("metaResultObject is null");
179 }
180 if (this.resultLoader == null) {
181 throw new IllegalArgumentException("resultLoader is null");
182 }
183
184 this.load(null);
185 }
186
187 public void load(final Object userObject) throws SQLException {
188 if (this.metaResultObject == null || this.resultLoader == null) {
189 if (this.mappedParameter == null) {
190 throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
191 + "required parameter of mapped statement ["
192 + 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("Cannot lazy load property [" + this.property
199 + "] of deserialized object [" + userObject.getClass()
200 + "] because configuration does not contain statement ["
201 + this.mappedStatement + "]");
202 }
203
204 this.metaResultObject = config.newMetaObject(userObject);
205 this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
206 metaResultObject.getSetterType(this.property), null, null);
207 }
208
209
210
211
212
213 if (this.serializationCheck == null) {
214 final ResultLoader old = this.resultLoader;
215 this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
216 old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
217 }
218
219 this.metaResultObject.setValue(property, this.resultLoader.loadResult());
220 }
221
222 private Configuration getConfiguration() {
223 if (this.configurationFactory == null) {
224 throw new ExecutorException("Cannot get Configuration as configuration factory was not set.");
225 }
226
227 Object configurationObject;
228 try {
229 final Method factoryMethod = this.configurationFactory.getDeclaredMethod(FACTORY_METHOD);
230 if (!Modifier.isStatic(factoryMethod.getModifiers())) {
231 throw new ExecutorException("Cannot get Configuration as factory method ["
232 + this.configurationFactory + "]#["
233 + FACTORY_METHOD + "] is not static.");
234 }
235
236 if (!factoryMethod.isAccessible()) {
237 configurationObject = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
238 try {
239 factoryMethod.setAccessible(true);
240 return factoryMethod.invoke(null);
241 } finally {
242 factoryMethod.setAccessible(false);
243 }
244 });
245 } else {
246 configurationObject = factoryMethod.invoke(null);
247 }
248 } catch (final ExecutorException ex) {
249 throw ex;
250 } catch (final NoSuchMethodException ex) {
251 throw new ExecutorException("Cannot get Configuration as factory class ["
252 + this.configurationFactory + "] is missing factory method of name ["
253 + FACTORY_METHOD + "].", ex);
254 } catch (final PrivilegedActionException ex) {
255 throw new ExecutorException("Cannot get Configuration as factory method ["
256 + this.configurationFactory + "]#["
257 + FACTORY_METHOD + "] threw an exception.", ex.getCause());
258 } catch (final Exception ex) {
259 throw new ExecutorException("Cannot get Configuration as factory method ["
260 + this.configurationFactory + "]#["
261 + FACTORY_METHOD + "] threw an exception.", ex);
262 }
263
264 if (!(configurationObject instanceof Configuration)) {
265 throw new ExecutorException("Cannot get Configuration as factory method ["
266 + this.configurationFactory + "]#["
267 + FACTORY_METHOD + "] didn't return [" + Configuration.class + "] but ["
268 + (configurationObject == null ? "null" : configurationObject.getClass()) + "].");
269 }
270
271 return Configuration.class.cast(configurationObject);
272 }
273
274 private Log getLogger() {
275 if (this.log == null) {
276 this.log = LogFactory.getLog(this.getClass());
277 }
278 return this.log;
279 }
280 }
281
282 private static final class ClosedExecutor extends BaseExecutor {
283
284 public ClosedExecutor() {
285 super(null, null);
286 }
287
288 @Override
289 public boolean isClosed() {
290 return true;
291 }
292
293 @Override
294 protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
295 throw new UnsupportedOperationException("Not supported.");
296 }
297
298 @Override
299 protected List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
300 throw new UnsupportedOperationException("Not supported.");
301 }
302
303 @Override
304 protected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
305 throw new UnsupportedOperationException("Not supported.");
306 }
307
308 @Override
309 protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
310 throw new UnsupportedOperationException("Not supported.");
311 }
312 }
313 }