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