1 /**
2 * Copyright (C) 2006 Google Inc.
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 * http://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
17 package com.google.inject.struts2;
18
19 import com.google.inject.AbstractModule;
20 import com.google.inject.Binder;
21 import com.google.inject.Guice;
22 import com.google.inject.Injector;
23 import com.google.inject.Module;
24 import com.google.inject.ScopeAnnotation;
25 import com.google.inject.servlet.ServletModule;
26 import com.opensymphony.xwork2.ActionInvocation;
27 import com.opensymphony.xwork2.ObjectFactory;
28 import com.opensymphony.xwork2.config.ConfigurationException;
29 import com.opensymphony.xwork2.config.entities.InterceptorConfig;
30 import com.opensymphony.xwork2.inject.Inject;
31 import com.opensymphony.xwork2.interceptor.Interceptor;
32 import java.lang.annotation.Annotation;
33 import java.util.ArrayList;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.logging.Logger;
39
40 public class GuiceObjectFactory extends ObjectFactory {
41
42 static final Logger logger =
43 Logger.getLogger(GuiceObjectFactory.class.getName());
44
45 Module module;
46 volatile Injector injector;
47 boolean developmentMode = false;
48 List<ProvidedInterceptor> interceptors
49 = new ArrayList<ProvidedInterceptor>();
50
51 @Override
52 public boolean isNoArgConstructorRequired() {
53 return false;
54 }
55
56 @Inject(value = "guice.module", required = false)
57 void setModule(String moduleClassName) {
58 try {
59 // Instantiate user's module.
60 @SuppressWarnings({"unchecked"})
61 Class<? extends Module> moduleClass =
62 (Class<? extends Module>) Class.forName(moduleClassName);
63 this.module = moduleClass.newInstance();
64 } catch (Exception e) {
65 throw new RuntimeException(e);
66 }
67 }
68
69 @Inject(value = "struts.devMode", required = false)
70 void setDevelopmentMode(String developmentMode) {
71 this.developmentMode = developmentMode.trim().equals("true");
72 }
73
74 Set<Class<?>> boundClasses = new HashSet<Class<?>>();
75
76 public Class getClassInstance(String name) throws ClassNotFoundException {
77 Class<?> clazz = super.getClassInstance(name);
78
79 synchronized (this) {
80 if (injector == null) {
81 // We can only bind each class once.
82 if (!boundClasses.contains(clazz)) {
83 try {
84 // Calling these methods now helps us detect ClassNotFoundErrors
85 // early.
86 clazz.getDeclaredFields();
87 clazz.getDeclaredMethods();
88
89 boundClasses.add(clazz);
90 } catch (Throwable t) {
91 // Struts should still work even though some classes aren't in the
92 // classpath. It appears we always get the exception here when
93 // this is the case.
94 return clazz;
95 }
96 }
97 }
98 }
99
100 return clazz;
101 }
102
103 @SuppressWarnings("unchecked")
104 public Object buildBean(Class clazz, Map extraContext) {
105 if (injector == null) {
106 synchronized (this) {
107 if (injector == null) {
108 createInjector();
109 }
110 }
111 }
112
113 return injector.getInstance(clazz);
114 }
115
116 private void createInjector() {
117 try {
118 logger.info("Creating injector...");
119 this.injector = Guice.createInjector(new AbstractModule() {
120 protected void configure() {
121 // Install default servlet bindings.
122 install(new ServletModule());
123
124 // Install user's module.
125 if (module != null) {
126 logger.info("Installing " + module + "...");
127 install(module);
128 }
129 else {
130 logger.info("No module found. Set 'guice.module' to a Module "
131 + "class name if you'd like to use one.");
132 }
133
134 // Tell the injector about all the action classes, etc., so it
135 // can validate them at startup.
136 for (Class<?> boundClass : boundClasses) {
137 // TODO: Set source from Struts XML.
138 bind(boundClass);
139 }
140
141 // Validate the interceptor class.
142 for (ProvidedInterceptor interceptor : interceptors) {
143 interceptor.validate(binder());
144 }
145 }
146 });
147
148 // Inject interceptors.
149 for (ProvidedInterceptor interceptor : interceptors) {
150 interceptor.inject();
151 }
152
153 } catch (Throwable t) {
154 t.printStackTrace();
155 System.exit(1);
156 }
157 logger.info("Injector created successfully.");
158 }
159
160 @SuppressWarnings("unchecked")
161 public Interceptor buildInterceptor(InterceptorConfig interceptorConfig,
162 Map interceptorRefParams) throws ConfigurationException {
163 // Ensure the interceptor class is present.
164 Class<? extends Interceptor> interceptorClass;
165 try {
166 interceptorClass = getClassInstance(interceptorConfig.getClassName());
167 } catch (ClassNotFoundException e) {
168 throw new RuntimeException(e);
169 }
170
171 ProvidedInterceptor providedInterceptor = new ProvidedInterceptor(
172 interceptorConfig, interceptorRefParams, interceptorClass);
173 interceptors.add(providedInterceptor);
174 return providedInterceptor;
175 }
176
177 Interceptor superBuildInterceptor(InterceptorConfig interceptorConfig,
178 Map interceptorRefParams) throws ConfigurationException {
179 return super.buildInterceptor(interceptorConfig, interceptorRefParams);
180 }
181
182 class ProvidedInterceptor implements Interceptor {
183
184 final InterceptorConfig config;
185 final Map params;
186 final Class<? extends Interceptor> interceptorClass;
187 Interceptor delegate;
188
189 ProvidedInterceptor(InterceptorConfig config, Map params,
190 Class<? extends Interceptor> interceptorClass) {
191 this.config = config;
192 this.params = params;
193 this.interceptorClass = interceptorClass;
194 }
195
196 void validate(Binder binder) {
197 // TODO: Set source from Struts XML.
198 if (hasScope(interceptorClass)) {
199 binder.addError("Scoping interceptors is not currently supported."
200 + " Please remove the scope annotation from "
201 + interceptorClass.getName() + ".");
202 }
203
204 // Make sure it implements Interceptor.
205 if (!Interceptor.class.isAssignableFrom(interceptorClass)) {
206 binder.addError(interceptorClass.getName() + " must implement "
207 + Interceptor.class.getName() + ".");
208 }
209 }
210
211 void inject() {
212 delegate = superBuildInterceptor(config, params);
213 }
214
215 public void destroy() {
216 delegate.destroy();
217 }
218
219 public void init() {
220 throw new AssertionError();
221 }
222
223 public String intercept(ActionInvocation invocation) throws Exception {
224 return delegate.intercept(invocation);
225 }
226 }
227
228 /**
229 * Returns true if the given class has a scope annotation.
230 */
231 private static boolean hasScope(
232 Class<? extends Interceptor> interceptorClass) {
233 for (Annotation annotation : interceptorClass.getAnnotations()) {
234 if (annotation.annotationType()
235 .isAnnotationPresent(ScopeAnnotation.class)) {
236 return true;
237 }
238 }
239 return false;
240 }
241 }