1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.cdi;
17
18 import jakarta.enterprise.event.Observes;
19 import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
20 import jakarta.enterprise.inject.spi.AnnotatedMember;
21 import jakarta.enterprise.inject.spi.AnnotatedType;
22 import jakarta.enterprise.inject.spi.Extension;
23 import jakarta.enterprise.inject.spi.InjectionPoint;
24 import jakarta.enterprise.inject.spi.InjectionTarget;
25 import jakarta.enterprise.inject.spi.ProcessAnnotatedType;
26 import jakarta.enterprise.inject.spi.ProcessInjectionTarget;
27 import jakarta.enterprise.inject.spi.ProcessProducer;
28 import jakarta.enterprise.inject.spi.WithAnnotations;
29 import jakarta.inject.Named;
30 import jakarta.inject.Qualifier;
31
32 import java.lang.annotation.Annotation;
33 import java.lang.reflect.Type;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Set;
39
40 import org.apache.ibatis.session.SqlSession;
41 import org.apache.ibatis.session.SqlSessionFactory;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46
47
48
49
50 public class MybatisExtension implements Extension {
51
52 private static final Logger LOGGER = LoggerFactory.getLogger(MybatisExtension.class.getName());
53
54 private final Set<BeanKey> sessionProducers = new HashSet<>();
55
56 private final Set<Type> mapperTypes = new HashSet<>();
57
58 private final Set<InjectionPoint> injectionPoints = new HashSet<>();
59
60
61
62
63
64
65
66
67
68 protected <T> void processAnnotatedType(
69 @Observes @WithAnnotations({ Mapper.class }) final ProcessAnnotatedType<T> pat) {
70 final AnnotatedType<T> at = pat.getAnnotatedType();
71 if (at.isAnnotationPresent(Mapper.class)) {
72 LOGGER.info("MyBatis CDI Module - Found class with @Mapper-annotation: {}", at.getJavaClass().getSimpleName());
73 this.mapperTypes.add(at.getBaseType());
74 }
75 }
76
77
78
79
80
81
82
83
84
85
86
87 @SuppressWarnings("unchecked")
88 protected <T, X> void processProducer(@Observes final ProcessProducer<T, X> pp) {
89 final AnnotatedMember<T> am = pp.getAnnotatedMember();
90 final boolean isAnnotated = am.isAnnotationPresent(SessionFactoryProvider.class);
91 final boolean isSqlSessionFactory = am.getBaseType().equals(SqlSessionFactory.class);
92 final String logData = String.join(".", am.getJavaMember().getDeclaringClass().getSimpleName(),
93 am.getJavaMember().getName());
94 if (isAnnotated) {
95 if (isSqlSessionFactory) {
96 LOGGER.info("MyBatis CDI Module - SqlSessionFactory producer {}", logData);
97 this.sessionProducers.add(new BeanKey((Class<Type>) (Type) SqlSession.class, am.getAnnotations()));
98 } else {
99 LOGGER.error("MyBatis CDI Module - Invalid return type (Must be SqlSessionFactory): {}", logData);
100 pp.addDefinitionError(new MybatisCdiConfigurationException(
101 String.format("SessionFactoryProvider producers must return SqlSessionFactory (%s)", logData)));
102 }
103 } else if (isSqlSessionFactory) {
104 LOGGER.warn(
105 "MyBatis CDI Module - Ignored SqlSessionFactory producer because it is not annotated with @SessionFactoryProvider: {}",
106 logData);
107 }
108 }
109
110
111
112
113
114
115
116
117
118 protected <X> void processInjectionTarget(@Observes ProcessInjectionTarget<X> event) {
119 final InjectionTarget<X> it = event.getInjectionTarget();
120 this.injectionPoints.addAll(it.getInjectionPoints());
121 }
122
123
124
125
126
127
128
129 @SuppressWarnings("unchecked")
130 protected void afterBeanDiscovery(@Observes final AfterBeanDiscovery abd) {
131 LOGGER.info("MyBatis CDI Module - Activated");
132
133 Set<BeanKey> mappers = new HashSet<>();
134 Set<BeanKey> sessionTargets = new HashSet<>();
135
136 for (InjectionPoint ip : injectionPoints) {
137 if (this.mapperTypes.contains(ip.getAnnotated().getBaseType())) {
138 LOGGER.info("MyBatis CDI Module - Found a bean, which needs a Mapper {}", ip.getAnnotated().getBaseType());
139 mappers.add(new BeanKey((Class<Type>) ip.getAnnotated().getBaseType(), ip.getAnnotated().getAnnotations()));
140 } else if (SqlSession.class.equals(ip.getAnnotated().getBaseType())) {
141 sessionTargets
142 .add(new BeanKey((Class<Type>) ip.getAnnotated().getBaseType(), ip.getAnnotated().getAnnotations()));
143 }
144 }
145 this.injectionPoints.clear();
146
147
148 for (BeanKey key : mappers) {
149 LOGGER.info("MyBatis CDI Module - Managed Mapper dependency: {}, {}", key.getKey(), key.type.getName());
150 abd.addBean(key.createBean());
151 }
152 this.mapperTypes.clear();
153
154
155 for (BeanKey key : this.sessionProducers) {
156 LOGGER.info("MyBatis CDI Module - Managed SqlSession: {}, {}", key.getKey(), key.type.getName());
157 abd.addBean(key.createBean());
158 sessionTargets.remove(key);
159 }
160 this.sessionProducers.clear();
161
162
163 for (BeanKey key : sessionTargets) {
164 LOGGER.warn("MyBatis CDI Module - Unmanaged SqlSession: {}, {}", key.getKey(), key.type.getName());
165 }
166
167 }
168
169
170
171
172 private static final class BeanKey implements Comparable<BeanKey> {
173
174 private final String key;
175
176 private final List<Annotation> qualifiers;
177
178 private final Class<Type> type;
179
180 private final String sqlSessionManagerName;
181
182 public BeanKey(Class<Type> type, Set<Annotation> annotations) {
183 this.type = type;
184 this.qualifiers = sort(filterQualifiers(annotations));
185
186
187 final StringBuilder sb = new StringBuilder();
188 String name = null;
189 sb.append(type.getName());
190 for (Annotation q : this.qualifiers) {
191 if (q instanceof Named) {
192 name = ((Named) q).value();
193 } else {
194 sb.append(".").append(q.annotationType().getSimpleName());
195 }
196 }
197 if (name != null) {
198 sb.append("_").append(name);
199 }
200 this.key = sb.toString();
201 this.sqlSessionManagerName = name;
202 }
203
204 private Set<Annotation> filterQualifiers(Set<Annotation> annotations) {
205 final Set<Annotation> set = new HashSet<>();
206 for (Annotation a : annotations) {
207 if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
208 set.add(a);
209 }
210 }
211 return set;
212 }
213
214 private List<Annotation> sort(Set<Annotation> annotations) {
215 final List<Annotation> list = new ArrayList<>(annotations);
216 Collections.sort(list, (a, b) -> a.getClass().getName().compareTo(b.getClass().getName()));
217 return list;
218 }
219
220 @Override
221 public int compareTo(BeanKey o) {
222 return this.key.compareTo(o.key);
223 }
224
225 @Override
226 public int hashCode() {
227 int hash = 3;
228 return 43 * hash + (this.key != null ? this.key.hashCode() : 0);
229 }
230
231 @Override
232 public boolean equals(Object obj) {
233 if (obj == null || this.getClass() != obj.getClass()) {
234 return false;
235 }
236 final BeanKey other = (BeanKey) obj;
237 return !(this.key == null ? other.key != null : !this.key.equals(other.key));
238 }
239
240 public MyBatisBean createBean() {
241 return new MyBatisBean(this.key, this.type, new HashSet<>(this.qualifiers), this.sqlSessionManagerName);
242 }
243
244 public String getKey() {
245 return this.key;
246 }
247
248 @Override
249 public String toString() {
250 return this.key;
251 }
252
253 }
254
255 }