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 }