• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.google.inject.struts2;
18 
19 import com.google.inject.AbstractModule;
20 import com.google.inject.Binder;
21 import com.google.inject.Injector;
22 import com.google.inject.internal.Annotations;
23 
24 import com.opensymphony.xwork2.ActionInvocation;
25 import com.opensymphony.xwork2.ObjectFactory;
26 import com.opensymphony.xwork2.config.ConfigurationException;
27 import com.opensymphony.xwork2.config.entities.InterceptorConfig;
28 import com.opensymphony.xwork2.inject.Inject;
29 import com.opensymphony.xwork2.interceptor.Interceptor;
30 
31 import java.lang.annotation.Annotation;
32 import java.util.ArrayList;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import 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  */
46 public 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
isNoArgConstructorRequired()60   public boolean isNoArgConstructorRequired() {
61     return false;
62   }
63 
64   @Inject(value = "guice.module", required = false)
setModule(String moduleClassName)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 
getClassInstance(String name)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")
buildBean(Class clazz, Map<String, Object> extraContext)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 
createInjector()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")
buildInterceptor(InterceptorConfig interceptorConfig, Map interceptorRefParams)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 
superBuildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams)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 
ProvidedInterceptor(InterceptorConfig config, Map<String, String> params, Class<? extends Interceptor> interceptorClass)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 
validate(Binder binder)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 
inject()201     void inject() {
202       delegate = superBuildInterceptor(config, params);
203     }
204 
destroy()205     public void destroy() {
206       if (null != delegate) {
207         delegate.destroy();
208       }
209     }
210 
init()211     public void init() {
212       throw new AssertionError();
213     }
214 
intercept(ActionInvocation invocation)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    */
hasScope(Class<? extends Interceptor> interceptorClass)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 }
232