/extensions/struts2/src/com/google/inject/struts2/Struts2Factory.java
Java | 231 lines | 153 code | 37 blank | 41 comment | 18 complexity | 577cc8a5c5e605f0ac617a781008b4ea MD5 | raw file
1/**
2 * Copyright (C) 2009 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
17package com.google.inject.struts2;
18
19import com.google.inject.AbstractModule;
20import com.google.inject.Binder;
21import com.google.inject.Injector;
22import com.google.inject.internal.Annotations;
23
24import com.opensymphony.xwork2.ActionInvocation;
25import com.opensymphony.xwork2.ObjectFactory;
26import com.opensymphony.xwork2.config.ConfigurationException;
27import com.opensymphony.xwork2.config.entities.InterceptorConfig;
28import com.opensymphony.xwork2.inject.Inject;
29import com.opensymphony.xwork2.interceptor.Interceptor;
30
31import java.lang.annotation.Annotation;
32import java.util.ArrayList;
33import java.util.HashSet;
34import java.util.List;
35import java.util.Map;
36import java.util.Set;
37import java.util.logging.Logger;
38
39/**
40 * Cleanup up version from Bob's GuiceObjectFactory. Now works properly with
41 * GS2 and fixes several bugs.
42 *
43 * @author dhanji@gmail.com
44 * @author benmccann.com
45 */
46public class Struts2Factory extends ObjectFactory {
47
48 private static final long serialVersionUID = 1L;
49 private static final Logger logger = Logger.getLogger(Struts2Factory.class.getName());
50 private static final String ERROR_NO_INJECTOR =
51 "Cannot find a Guice injector. Are you sure you registered a GuiceServletContextListener "
52 + "that uses the Struts2GuicePluginModule in your application's web.xml?";
53
54 private static @com.google.inject.Inject Injector injector;
55
56 private final List<ProvidedInterceptor> interceptors = new ArrayList<ProvidedInterceptor>();
57 private volatile Injector strutsInjector;
58
59 @Override
60 public boolean isNoArgConstructorRequired() {
61 return false;
62 }
63
64 @Inject(value = "guice.module", required = false)
65 void setModule(String moduleClassName) {
66 throw new RuntimeException("The struts2 plugin no longer supports"
67 + " specifying a module via the 'guice.module' property in XML."
68 + " Please install your module via a GuiceServletContextListener instead.");
69 }
70
71 Set<Class<?>> boundClasses = new HashSet<Class<?>>();
72
73 public Class<?> getClassInstance(String name) throws ClassNotFoundException {
74 Class<?> clazz = super.getClassInstance(name);
75
76 synchronized (this) {
77 if (strutsInjector == null) {
78 // We can only bind each class once.
79 if (!boundClasses.contains(clazz)) {
80 try {
81 // Calling these methods now helps us detect ClassNotFoundErrors
82 // early.
83 clazz.getDeclaredFields();
84 clazz.getDeclaredMethods();
85
86 boundClasses.add(clazz);
87 } catch (Throwable t) {
88 // Struts should still work even though some classes aren't in the
89 // classpath. It appears we always get the exception here when
90 // this is the case.
91 return clazz;
92 }
93 }
94 }
95 }
96
97 return clazz;
98 }
99
100 @Override @SuppressWarnings("unchecked")
101 public Object buildBean(Class clazz, Map<String, Object> extraContext) {
102 if (strutsInjector == null) {
103 synchronized (this) {
104 if (strutsInjector == null) {
105 createInjector();
106 }
107 }
108 }
109 return strutsInjector.getInstance(clazz);
110 }
111
112 private void createInjector() {
113 logger.info("Loading struts2 Guice support...");
114
115 // Something is wrong, since this should be there if GuiceServletContextListener
116 // was present.
117 if (injector == null) {
118 logger.severe(ERROR_NO_INJECTOR);
119 throw new RuntimeException(ERROR_NO_INJECTOR);
120 }
121
122 this.strutsInjector = injector.createChildInjector(new AbstractModule() {
123 protected void configure() {
124
125 // Tell the injector about all the action classes, etc., so it
126 // can validate them at startup.
127 for (Class<?> boundClass : boundClasses) {
128 // TODO: Set source from Struts XML.
129 bind(boundClass);
130 }
131
132 // Validate the interceptor class.
133 for (ProvidedInterceptor interceptor : interceptors) {
134 interceptor.validate(binder());
135 }
136 }
137 });
138
139 // Inject interceptors.
140 for (ProvidedInterceptor interceptor : interceptors) {
141 interceptor.inject();
142 }
143
144 logger.info("Injector created successfully.");
145 }
146
147 @SuppressWarnings("unchecked")
148 public Interceptor buildInterceptor(InterceptorConfig interceptorConfig,
149 Map interceptorRefParams) throws ConfigurationException {
150 // Ensure the interceptor class is present.
151 Class<? extends Interceptor> interceptorClass;
152 try {
153 interceptorClass = (Class<? extends Interceptor>)
154 getClassInstance(interceptorConfig.getClassName());
155 } catch (ClassNotFoundException e) {
156 throw new RuntimeException(e);
157 }
158
159 ProvidedInterceptor providedInterceptor = new ProvidedInterceptor(
160 interceptorConfig, interceptorRefParams, interceptorClass);
161 interceptors.add(providedInterceptor);
162 return providedInterceptor;
163 }
164
165 private Interceptor superBuildInterceptor(InterceptorConfig interceptorConfig,
166 Map<String, String> interceptorRefParams) throws ConfigurationException {
167 return super.buildInterceptor(interceptorConfig, interceptorRefParams);
168 }
169
170 private class ProvidedInterceptor implements Interceptor {
171
172 private static final long serialVersionUID = 1L;
173
174 private final InterceptorConfig config;
175 private final Map<String, String> params;
176 private final Class<? extends Interceptor> interceptorClass;
177 private Interceptor delegate;
178
179 ProvidedInterceptor(InterceptorConfig config, Map<String, String> params,
180 Class<? extends Interceptor> interceptorClass) {
181 this.config = config;
182 this.params = params;
183 this.interceptorClass = interceptorClass;
184 }
185
186 void validate(Binder binder) {
187 // TODO: Set source from Struts XML.
188 if (hasScope(interceptorClass)) {
189 binder.addError("Scoping interceptors is not currently supported."
190 + " Please remove the scope annotation from "
191 + interceptorClass.getName() + ".");
192 }
193
194 // Make sure it implements Interceptor.
195 if (!Interceptor.class.isAssignableFrom(interceptorClass)) {
196 binder.addError(interceptorClass.getName() + " must implement "
197 + Interceptor.class.getName() + ".");
198 }
199 }
200
201 void inject() {
202 delegate = superBuildInterceptor(config, params);
203 }
204
205 public void destroy() {
206 if (null != delegate) {
207 delegate.destroy();
208 }
209 }
210
211 public void init() {
212 throw new AssertionError();
213 }
214
215 public String intercept(ActionInvocation invocation) throws Exception {
216 return delegate.intercept(invocation);
217 }
218 }
219
220 /**
221 * Returns true if the given class has a scope annotation.
222 */
223 private static boolean hasScope(Class<? extends Interceptor> interceptorClass) {
224 for (Annotation annotation : interceptorClass.getAnnotations()) {
225 if (Annotations.isScopeAnnotation(annotation.annotationType())) {
226 return true;
227 }
228 }
229 return false;
230 }
231}