• 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 import com.opensymphony.xwork2.ActionInvocation;
24 import com.opensymphony.xwork2.ObjectFactory;
25 import com.opensymphony.xwork2.config.ConfigurationException;
26 import com.opensymphony.xwork2.config.entities.InterceptorConfig;
27 import com.opensymphony.xwork2.inject.Inject;
28 import com.opensymphony.xwork2.interceptor.Interceptor;
29 import java.lang.annotation.Annotation;
30 import java.util.ArrayList;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.logging.Logger;
36 
37 /**
38  * Cleanup up version from Bob's GuiceObjectFactory. Now works properly with GS2 and fixes several
39  * bugs.
40  *
41  * @author dhanji@gmail.com
42  * @author benmccann.com
43  */
44 public class Struts2Factory extends ObjectFactory {
45 
46   private static final long serialVersionUID = 1L;
47   private static final Logger logger = Logger.getLogger(Struts2Factory.class.getName());
48   private static final String ERROR_NO_INJECTOR =
49       "Cannot find a Guice injector.  Are you sure you registered a GuiceServletContextListener "
50           + "that uses the Struts2GuicePluginModule in your application's web.xml?";
51 
52   private static @com.google.inject.Inject Injector injector;
53 
54   private final List<ProvidedInterceptor> interceptors = new ArrayList<>();
55   private volatile Injector strutsInjector;
56 
57   @Override
isNoArgConstructorRequired()58   public boolean isNoArgConstructorRequired() {
59     return false;
60   }
61 
62   @Inject(value = "guice.module", required = false)
setModule(String moduleClassName)63   void setModule(String moduleClassName) {
64     throw new RuntimeException(
65         "The struts2 plugin no longer supports"
66             + " specifying a module via the 'guice.module' property in XML."
67             + " Please install your module via a GuiceServletContextListener instead.");
68   }
69 
70   Set<Class<?>> boundClasses = new HashSet<>();
71 
72   @Override
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
101   @SuppressWarnings("unchecked")
buildBean(Class clazz, Map<String, Object> extraContext)102   public Object buildBean(Class clazz, Map<String, Object> extraContext) {
103     if (strutsInjector == null) {
104       synchronized (this) {
105         if (strutsInjector == null) {
106           createInjector();
107         }
108       }
109     }
110     return strutsInjector.getInstance(clazz);
111   }
112 
createInjector()113   private void createInjector() {
114     logger.info("Loading struts2 Guice support...");
115 
116     // Something is wrong, since this should be there if GuiceServletContextListener
117     // was present.
118     if (injector == null) {
119       logger.severe(ERROR_NO_INJECTOR);
120       throw new RuntimeException(ERROR_NO_INJECTOR);
121     }
122 
123     this.strutsInjector =
124         injector.createChildInjector(
125             new AbstractModule() {
126               @Override
127               protected void configure() {
128 
129                 // Tell the injector about all the action classes, etc., so it
130                 // can validate them at startup.
131                 for (Class<?> boundClass : boundClasses) {
132                   // TODO: Set source from Struts XML.
133                   bind(boundClass);
134                 }
135 
136                 // Validate the interceptor class.
137                 for (ProvidedInterceptor interceptor : interceptors) {
138                   interceptor.validate(binder());
139                 }
140               }
141             });
142 
143     // Inject interceptors.
144     for (ProvidedInterceptor interceptor : interceptors) {
145       interceptor.inject();
146     }
147 
148     logger.info("Injector created successfully.");
149   }
150 
151   @Override
152   @SuppressWarnings("unchecked")
buildInterceptor(InterceptorConfig interceptorConfig, Map interceptorRefParams)153   public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map interceptorRefParams)
154       throws ConfigurationException {
155     // Ensure the interceptor class is present.
156     Class<? extends Interceptor> interceptorClass;
157     try {
158       interceptorClass =
159           (Class<? extends Interceptor>) getClassInstance(interceptorConfig.getClassName());
160     } catch (ClassNotFoundException e) {
161       throw new RuntimeException(e);
162     }
163 
164     ProvidedInterceptor providedInterceptor =
165         new ProvidedInterceptor(interceptorConfig, interceptorRefParams, interceptorClass);
166     interceptors.add(providedInterceptor);
167     if (strutsInjector != null) {
168       synchronized (this) {
169         if (strutsInjector != null) {
170           providedInterceptor.inject();
171         }
172       }
173     }
174     return providedInterceptor;
175   }
176 
superBuildInterceptor( InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams)177   private Interceptor superBuildInterceptor(
178       InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams)
179       throws ConfigurationException {
180     return super.buildInterceptor(interceptorConfig, interceptorRefParams);
181   }
182 
183   private class ProvidedInterceptor implements Interceptor {
184 
185     private static final long serialVersionUID = 1L;
186 
187     private final InterceptorConfig config;
188     private final Map<String, String> params;
189     private final Class<? extends Interceptor> interceptorClass;
190     private Interceptor delegate;
191 
ProvidedInterceptor( InterceptorConfig config, Map<String, String> params, Class<? extends Interceptor> interceptorClass)192     ProvidedInterceptor(
193         InterceptorConfig config,
194         Map<String, String> params,
195         Class<? extends Interceptor> interceptorClass) {
196       this.config = config;
197       this.params = params;
198       this.interceptorClass = interceptorClass;
199     }
200 
validate(Binder binder)201     void validate(Binder binder) {
202       // TODO: Set source from Struts XML.
203       if (hasScope(interceptorClass)) {
204         binder.addError(
205             "Scoping interceptors is not currently supported."
206                 + " Please remove the scope annotation from "
207                 + interceptorClass.getName()
208                 + ".");
209       }
210 
211       // Make sure it implements Interceptor.
212       if (!Interceptor.class.isAssignableFrom(interceptorClass)) {
213         binder.addError(
214             interceptorClass.getName() + " must implement " + Interceptor.class.getName() + ".");
215       }
216     }
217 
inject()218     void inject() {
219       delegate = superBuildInterceptor(config, params);
220     }
221 
222     @Override
destroy()223     public void destroy() {
224       if (null != delegate) {
225         delegate.destroy();
226       }
227     }
228 
229     @Override
init()230     public void init() {
231       throw new AssertionError();
232     }
233 
234     @Override
intercept(ActionInvocation invocation)235     public String intercept(ActionInvocation invocation) throws Exception {
236       return delegate.intercept(invocation);
237     }
238   }
239 
240   /** Returns true if the given class has a scope annotation. */
hasScope(Class<? extends Interceptor> interceptorClass)241   private static boolean hasScope(Class<? extends Interceptor> interceptorClass) {
242     for (Annotation annotation : interceptorClass.getAnnotations()) {
243       if (Annotations.isScopeAnnotation(annotation.annotationType())) {
244         return true;
245       }
246     }
247     return false;
248   }
249 }
250