001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.tide.spring; 023 024import java.lang.annotation.Annotation; 025import java.lang.reflect.Constructor; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import javax.servlet.ServletContext; 034 035import org.granite.context.GraniteContext; 036import org.granite.logging.Logger; 037import org.granite.messaging.service.ServiceException; 038import org.granite.messaging.service.ServiceInvocationContext; 039import org.granite.messaging.webapp.HttpGraniteContext; 040import org.granite.tide.IInvocationCall; 041import org.granite.tide.IInvocationResult; 042import org.granite.tide.TidePersistenceManager; 043import org.granite.tide.TideServiceContext; 044import org.granite.tide.TideTransactionManager; 045import org.granite.tide.annotations.BypassTideMerge; 046import org.granite.tide.async.AsyncPublisher; 047import org.granite.tide.data.DataContext; 048import org.granite.tide.data.DataUpdatePostprocessor; 049import org.granite.tide.invocation.ContextUpdate; 050import org.granite.tide.invocation.InvocationResult; 051import org.granite.util.TypeUtil; 052import org.springframework.aop.framework.Advised; 053import org.springframework.aop.support.AopUtils; 054import org.springframework.beans.BeansException; 055import org.springframework.beans.factory.NoSuchBeanDefinitionException; 056import org.springframework.context.ApplicationContext; 057import org.springframework.context.ApplicationContextAware; 058import org.springframework.orm.jpa.EntityManagerFactoryInfo; 059import org.springframework.transaction.PlatformTransactionManager; 060import org.springframework.web.context.support.WebApplicationContextUtils; 061 062 063/** 064 * @author Sebastien Deleuze 065 * @author William DRAI 066 */ 067public class SpringServiceContext extends TideServiceContext implements ApplicationContextAware { 068 069 private static final long serialVersionUID = 1L; 070 071 protected transient ApplicationContext springContext = null; 072 073 private String persistenceManagerBeanName = null; 074 private String entityManagerFactoryBeanName = null; 075 076 private static final Logger log = Logger.getLogger(SpringServiceContext.class); 077 078 public SpringServiceContext() throws ServiceException { 079 super(); 080 081 log.debug("Getting spring context from container"); 082 getSpringContext(); 083 } 084 085 public SpringServiceContext(ApplicationContext springContext) throws ServiceException { 086 super(); 087 088 this.springContext = springContext; 089 } 090 091 public void setApplicationContext(ApplicationContext springContext) { 092 this.springContext = springContext; 093 } 094 095 protected ApplicationContext getSpringContext() { 096 if (springContext == null) { 097 GraniteContext context = GraniteContext.getCurrentInstance(); 098 ServletContext sc = ((HttpGraniteContext)context).getServletContext(); 099 springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(sc); 100 } 101 return springContext; 102 } 103 104 105 @Override 106 protected AsyncPublisher getAsyncPublisher() { 107 return null; 108 } 109 110 @Override 111 public Object findComponent(String componentName, Class<?> componentClass) { 112 Object bean = null; 113 String key = COMPONENT_ATTR + (componentName != null ? componentName : "_CLASS_" + componentClass.getName()); 114 115 GraniteContext context = GraniteContext.getCurrentInstance(); 116 if (context != null) { 117 bean = context.getRequestMap().get(key); 118 if (bean != null) 119 return bean; 120 } 121 122 ApplicationContext springContext = getSpringContext(); 123 try { 124 if (componentClass != null) { 125 Map<String, ?> beans = springContext.getBeansOfType(componentClass); 126 if (beans.size() == 1) 127 bean = beans.values().iterator().next(); 128 else if (beans.size() > 1 && componentName != null && !("".equals(componentName))) { 129 if (beans.containsKey(componentName)) 130 bean = beans.get(componentName); 131 } 132 else if (beans.isEmpty() && springContext.getClass().getName().indexOf("Grails") > 0 && componentClass.getName().endsWith("Service")) { 133 try { 134 Object serviceClass = springContext.getBean(componentClass.getName() + "ServiceClass"); 135 Method m = serviceClass.getClass().getMethod("getPropertyName"); 136 String compName = (String)m.invoke(serviceClass); 137 bean = springContext.getBean(compName); 138 } 139 catch (NoSuchMethodException e) { 140 log.error(e, "Could not get service class for %s", componentClass.getName()); 141 } 142 catch (InvocationTargetException e) { 143 log.error(e.getCause(), "Could not get service class for %s", componentClass.getName()); 144 } 145 catch (IllegalAccessException e) { 146 log.error(e.getCause(), "Could not get service class for %s", componentClass.getName()); 147 } 148 } 149 } 150 if (bean == null && componentName != null && !("".equals(componentName))) 151 bean = springContext.getBean(componentName); 152 153 if (context != null) 154 context.getRequestMap().put(key, bean); 155 return bean; 156 } 157 catch (NoSuchBeanDefinitionException nexc) { 158 if (componentName != null && componentName.endsWith("Controller")) { 159 try { 160 int idx = componentName.lastIndexOf("."); 161 String controllerName = idx > 0 162 ? componentName.substring(0, idx+1) + componentName.substring(idx+1, idx+2).toUpperCase() + componentName.substring(idx+2) 163 : componentName.substring(0, 1).toUpperCase() + componentName.substring(1); 164 bean = getSpringContext().getBean(controllerName); 165 if (context != null) 166 context.getRequestMap().put(key, bean); 167 return bean; 168 } 169 catch (NoSuchBeanDefinitionException nexc2) { 170 } 171 } 172 173 String msg = "Spring service named '" + componentName + "' does not exist."; 174 ServiceException e = new ServiceException(msg, nexc); 175 throw e; 176 } 177 catch (BeansException bexc) { 178 String msg = "Unable to create Spring service named '" + componentName + "'"; 179 ServiceException e = new ServiceException(msg, bexc); 180 throw e; 181 } 182 } 183 184 @Override 185 @SuppressWarnings("unchecked") 186 public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) { 187 String key = COMPONENT_CLASS_ATTR + componentName; 188 Set<Class<?>> classes = null; 189 GraniteContext context = GraniteContext.getCurrentInstance(); 190 if (context != null) { 191 classes = (Set<Class<?>>)context.getRequestMap().get(key); 192 if (classes != null) 193 return classes; 194 } 195 196 Object bean = findComponent(componentName, componentClass); 197 classes = buildComponentClasses(bean); 198 if (classes == null) 199 return null; 200 201 if (context != null) 202 context.getRequestMap().put(key, classes); 203 return classes; 204 } 205 206 protected Set<Class<?>> buildComponentClasses(Object bean) { 207 Set<Class<?>> classes = new HashSet<Class<?>>(); 208 for (Class<?> i : bean.getClass().getInterfaces()) 209 classes.add(i); 210 211 try { 212 while (bean instanceof Advised) 213 bean = ((Advised)bean).getTargetSource().getTarget(); 214 215 classes.add(AopUtils.getTargetClass(bean)); 216 } 217 catch (Exception e) { 218 log.warn(e, "Could not get AOP class for component " + bean.getClass()); 219 return null; 220 } 221 222 return classes; 223 } 224 225 protected boolean isBeanAnnotationPresent(Object bean, Class<? extends Annotation> annotationClass) { 226 if (bean.getClass().isAnnotationPresent(annotationClass)) 227 return true; 228 229 try { 230 while (bean instanceof Advised) 231 bean = ((Advised)bean).getTargetSource().getTarget(); 232 233 if (AopUtils.getTargetClass(bean).isAnnotationPresent(annotationClass)) 234 return true; 235 } 236 catch (Exception e) { 237 log.warn(e, "Could not get AOP class for component " + bean.getClass()); 238 } 239 240 return false; 241 } 242 243 protected boolean isBeanMethodAnnotationPresent(Object bean, String methodName, Class<?>[] methodArgTypes, Class<? extends Annotation> annotationClass) { 244 try { 245 Method m = bean.getClass().getMethod(methodName, methodArgTypes); 246 if (m.isAnnotationPresent(annotationClass)) 247 return true; 248 249 while (bean instanceof Advised) 250 bean = ((Advised)bean).getTargetSource().getTarget(); 251 252 m = AopUtils.getTargetClass(bean).getMethod(methodName, methodArgTypes); 253 if (m.isAnnotationPresent(annotationClass)) 254 return true; 255 } 256 catch (Exception e) { 257 log.warn("Could not find bean method", e); 258 } 259 260 return false; 261 } 262 263 264 @Override 265 public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) { 266 DataContext.init(); 267 268 DataUpdatePostprocessor dupp = (DataUpdatePostprocessor)findComponent(null, DataUpdatePostprocessor.class); 269 if (dupp != null) 270 DataContext.get().setDataUpdatePostprocessor(dupp); 271 } 272 273 @Override 274 public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) { 275 List<ContextUpdate> results = null; 276 DataContext dataContext = DataContext.get(); 277 Object[][] updates = dataContext != null ? dataContext.getUpdates() : null; 278 279 InvocationResult ires = new InvocationResult(result, results); 280 if (isBeanAnnotationPresent(context.getBean(), BypassTideMerge.class)) 281 ires.setMerge(false); 282 else if (isBeanMethodAnnotationPresent(context.getBean(), context.getMethod().getName(), context.getMethod().getParameterTypes(), BypassTideMerge.class)) 283 ires.setMerge(false); 284 285 ires.setUpdates(updates); 286 287 return ires; 288 } 289 290 @Override 291 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) { 292 } 293 294 295 public void setEntityManagerFactoryBeanName(String beanName) { 296 this.entityManagerFactoryBeanName = beanName; 297 } 298 299 public void setPersistenceManagerBeanName(String beanName) { 300 this.persistenceManagerBeanName = beanName; 301 } 302 303 /** 304 * Create a TidePersistenceManager 305 * 306 * @param create create if not existent (can be false for use in entity merge) 307 * @return a PersistenceContextManager 308 */ 309 @Override 310 protected TidePersistenceManager getTidePersistenceManager(boolean create) { 311 if (!create) 312 return null; 313 314 TidePersistenceManager pm = (TidePersistenceManager)GraniteContext.getCurrentInstance().getRequestMap().get(TidePersistenceManager.class.getName()); 315 if (pm != null) 316 return pm; 317 318 pm = createPersistenceManager(); 319 GraniteContext.getCurrentInstance().getRequestMap().put(TidePersistenceManager.class.getName(), pm); 320 return pm; 321 } 322 323 private TidePersistenceManager createPersistenceManager() { 324 if (persistenceManagerBeanName == null) { 325 if (entityManagerFactoryBeanName == null) { 326 // No bean or entity manager factory specified 327 328 // 1. Look for a TidePersistenceManager bean 329 Map<String, ?> pms = springContext.getBeansOfType(TidePersistenceManager.class); 330 if (pms.size() > 1) 331 throw new RuntimeException("More than one Tide persistence managers defined"); 332 333 if (pms.size() == 1) 334 return (TidePersistenceManager)pms.values().iterator().next(); 335 336 // 2. If not found, try to determine the Spring transaction manager 337 Map<String, ?> tms = springContext.getBeansOfType(PlatformTransactionManager.class); 338 if (tms.isEmpty()) 339 log.debug("No Spring transaction manager found, specify a persistence-manager-bean-name or entity-manager-factory-bean-name"); 340 else if (tms.size() > 1) 341 log.debug("More than one Spring transaction manager found, specify a persistence-manager-bean-name or entity-manager-factory-bean-name"); 342 else if (tms.size() == 1) { 343 PlatformTransactionManager ptm = (PlatformTransactionManager)tms.values().iterator().next(); 344 345 // If no entity manager, we define a Spring persistence manager 346 // that will try to infer persistence info from the Spring transaction manager 347 return new SpringPersistenceManager(ptm); 348 } 349 } 350 351 String emfBeanName = entityManagerFactoryBeanName != null ? entityManagerFactoryBeanName : "entityManagerFactory"; 352 try { 353 // Lookup the specified entity manager factory 354 Object emf = findComponent(emfBeanName, null); 355 356 // Try to determine the Spring transaction manager 357 TideTransactionManager tm = null; 358 Map<String, ?> ptms = springContext.getBeansOfType(PlatformTransactionManager.class); 359 if (ptms.size() == 1) { 360 log.debug("Found Spring transaction manager " + ptms.keySet().iterator().next()); 361 tm = new SpringTransactionManager((PlatformTransactionManager)ptms.values().iterator().next()); 362 } 363 364 Class<?> emfClass = TypeUtil.forName("javax.persistence.EntityManagerFactory"); 365 Class<?> pcmClass = TypeUtil.forName("org.granite.tide.data.JPAPersistenceManager"); 366 Constructor<?>[] cs = pcmClass.getConstructors(); 367 if (tm != null) { 368 for (Constructor<?> c : cs) { 369 if (c.getParameterTypes().length == 2 && emfClass.isAssignableFrom(c.getParameterTypes()[0]) 370 && TideTransactionManager.class.isAssignableFrom(c.getParameterTypes()[1])) { 371 log.debug("Created JPA persistence manager with Spring transaction manager"); 372 return (TidePersistenceManager)c.newInstance(((EntityManagerFactoryInfo)emf).getNativeEntityManagerFactory(), tm); 373 } 374 } 375 } 376 else { 377 for (Constructor<?> c : cs) { 378 if (c.getParameterTypes().length == 1 && emfClass.isAssignableFrom(c.getParameterTypes()[0])) { 379 log.debug("Created default JPA persistence manager"); 380 return (TidePersistenceManager)c.newInstance(emf); 381 } 382 } 383 } 384 385 throw new RuntimeException("Default Tide persistence manager not found"); 386 } 387 catch (ServiceException e) { 388 if (entityManagerFactoryBeanName != null) 389 log.debug("EntityManagerFactory named %s not found, JPA support disabled", emfBeanName); 390 391 return null; 392 } 393 catch (Exception e) { 394 throw new RuntimeException("Could not create default Tide persistence manager", e); 395 } 396 } 397 398 return (TidePersistenceManager)findComponent(persistenceManagerBeanName, null); 399 } 400}