1 /*
2 * Copyright 2004-2025 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.ibatis.sqlmap.engine.mapping.result;
17
18 import com.ibatis.common.resources.Resources;
19
20 import java.util.ArrayDeque;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Deque;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27
28 /**
29 * This class is used to create instances of result objects. It will use the configured ResultObjectFactory if there is
30 * one, otherwise it will use iBATIS' normal methods.
31 * <p>
32 * Note that this class is somewhat tightly coupled with SqlExecuter - SqlExecute must call the setStatementId() and
33 * setResultObjectFactory() methods before executing a statement. This is a result of using a ThreadLocal to hold the
34 * current configuration for the statement under execution. Using a ThreadLocal is a solution for IBATIS-366. Without a
35 * ThreadLocal, the current factory and statement id would have to be added to many method signatures - often in
36 * inappropriate places.
37 *
38 * @author Jeff Butler
39 */
40 public class ResultObjectFactoryUtil {
41
42 /**
43 * Use a ThreadLocal to hold the current statementId and factory. This is much easier than passing these items all
44 * over the place, and it has no measurable impact on performance (I did a test with 100000 result rows and found no
45 * impact - Jeff Butler).
46 */
47 private static ThreadLocal<Deque<FactorySettings>> factorySettings = new ThreadLocal<>();
48
49 /**
50 * Utility class - no instances.
51 */
52 private ResultObjectFactoryUtil() {
53 super();
54 }
55
56 /**
57 * Algorithm:
58 * <ul>
59 * <li>If factory is null, then create object internally()</li>
60 * <li>Otherwise try to create object through factory</li>
61 * <li>If null returned from factory, then create object internally</li>
62 * </ul>
63 * This allows the factory to selectively create objects, also allows for the common possibility that a factory is not
64 * configured.
65 *
66 * @param clazz
67 * the type of object to create
68 *
69 * @return a new instance of the specified class. The instance must be castable to the specified class.
70 *
71 * @throws InstantiationException
72 * if the instance cannot be created. If you throw this Exception, iBATIS will throw a runtime exception in
73 * response and will end.
74 * @throws IllegalAccessException
75 * if the constructor cannot be accessed. If you throw this Exception, iBATIS will throw a runtime exception
76 * in response and will end.
77 */
78 public static Object createObjectThroughFactory(Class clazz) throws InstantiationException, IllegalAccessException {
79
80 FactorySettings fs = getCurrentFactorySettings();
81
82 Object obj;
83 if (fs.getResultObjectFactory() == null) {
84 obj = createObjectInternally(clazz);
85 } else {
86 obj = fs.getResultObjectFactory().createInstance(fs.getStatementId(), clazz);
87 if (obj == null) {
88 obj = createObjectInternally(clazz);
89 }
90 }
91
92 return obj;
93 }
94
95 /**
96 * This method creates object using iBATIS' normal mechanism. We translate List and Collection to ArrayList, and Set
97 * to HashSet because these interfaces may be requested in nested resultMaps and we want to supply default
98 * implementations.
99 *
100 * @param clazz
101 * the clazz
102 *
103 * @return the object
104 *
105 * @throws InstantiationException
106 * the instantiation exception
107 * @throws IllegalAccessException
108 * the illegal access exception
109 */
110 private static Object createObjectInternally(Class clazz) throws InstantiationException, IllegalAccessException {
111 Class classToCreate;
112 if (clazz == List.class || clazz == Collection.class) {
113 classToCreate = ArrayList.class;
114 } else if (clazz == Set.class) {
115 classToCreate = HashSet.class;
116 } else {
117 classToCreate = clazz;
118 }
119
120 return Resources.instantiate(classToCreate);
121 }
122
123 /**
124 * This method pushes a new result object factory configuration onto the stack. We use a stack because the method can
125 * be called in a "nested" fashion if there are sub-selects. Calls to this method should be equally balanced with
126 * calls to cleanupResultObjectFactory().
127 *
128 * @param resultObjectFactory
129 * the result object factory
130 * @param statementId
131 * the statement id
132 */
133 public static void setupResultObjectFactory(ResultObjectFactory resultObjectFactory, String statementId) {
134 Deque<FactorySettings> fss = factorySettings.get();
135 if (fss == null) {
136 fss = new ArrayDeque<>();
137 factorySettings.set(fss);
138 }
139
140 FactorySettings fs = new FactorySettings();
141 fs.setResultObjectFactory(resultObjectFactory);
142 fs.setStatementId(statementId);
143 fss.push(fs);
144 }
145
146 /**
147 * Removes the FactorySettings bound to the current thread to avoid classloader leak issues. This method pops the top
148 * item off the stack, and kills the stack if there are no items left.
149 */
150 public static void cleanupResultObjectFactory() {
151 Deque<FactorySettings> fss = factorySettings.get();
152 if (!fss.isEmpty()) {
153 fss.pop();
154 }
155
156 if (fss.isEmpty()) {
157 factorySettings.remove();
158 }
159 }
160
161 /**
162 * Gets the current factory settings.
163 *
164 * @return the current factory settings
165 */
166 private static FactorySettings getCurrentFactorySettings() {
167 Deque<FactorySettings> fss = factorySettings.get();
168 FactorySettings fs;
169 if (fss == null || fss.isEmpty()) {
170 // this shouldn't happen if the SqlExecuter is behaving correctly
171 fs = new FactorySettings();
172 } else {
173 fs = fss.peek();
174 }
175
176 return fs;
177 }
178
179 /**
180 * The Class FactorySettings.
181 */
182 private static class FactorySettings {
183
184 /** The result object factory. */
185 private ResultObjectFactory resultObjectFactory;
186
187 /** The statement id. */
188 private String statementId;
189
190 /**
191 * Gets the result object factory.
192 *
193 * @return the result object factory
194 */
195 public ResultObjectFactory getResultObjectFactory() {
196 return resultObjectFactory;
197 }
198
199 /**
200 * Sets the result object factory.
201 *
202 * @param resultObjectFactory
203 * the new result object factory
204 */
205 public void setResultObjectFactory(ResultObjectFactory resultObjectFactory) {
206 this.resultObjectFactory = resultObjectFactory;
207 }
208
209 /**
210 * Gets the statement id.
211 *
212 * @return the statement id
213 */
214 public String getStatementId() {
215 return statementId;
216 }
217
218 /**
219 * Sets the statement id.
220 *
221 * @param statementId
222 * the new statement id
223 */
224 public void setStatementId(String statementId) {
225 this.statementId = statementId;
226 }
227 }
228 }