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