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