Home | History | Annotate | Download | only in struts2
      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 (at) 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
     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 }
    232