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 try { 123 bean = internalFindComponent(componentName, componentClass); 124 125 if (context != null) 126 context.getRequestMap().put(key, bean); 127 return bean; 128 } 129 catch (NoSuchBeanDefinitionException nexc) { 130 String msg = "Spring service named '" + componentName + "' does not exist."; 131 ServiceException e = new ServiceException(msg, nexc); 132 throw e; 133 } 134 catch (BeansException bexc) { 135 String msg = "Unable to create Spring service named '" + componentName + "'"; 136 ServiceException e = new ServiceException(msg, bexc); 137 throw e; 138 } 139 } 140 141 protected Object internalFindComponent(String componentName, Class<?> componentClass) { 142 Object bean = null; 143 144 if (componentClass != null) { 145 Map<String, ?> beans = springContext.getBeansOfType(componentClass); 146 if (beans.size() == 1) 147 bean = beans.values().iterator().next(); 148 else if (beans.size() > 1 && componentName != null && !("".equals(componentName))) { 149 if (beans.containsKey(componentName)) 150 bean = beans.get(componentName); 151 } 152 else if (beans.isEmpty() && springContext.getClass().getName().indexOf("Grails") > 0 && componentClass.getName().endsWith("Service")) { 153 try { 154 Object serviceClass = springContext.getBean(componentClass.getName() + "ServiceClass"); 155 Method m = serviceClass.getClass().getMethod("getPropertyName"); 156 String compName = (String)m.invoke(serviceClass); 157 bean = springContext.getBean(compName); 158 } 159 catch (NoSuchMethodException e) { 160 log.error(e, "Could not get service class for %s", componentClass.getName()); 161 } 162 catch (InvocationTargetException e) { 163 log.error(e.getCause(), "Could not get service class for %s", componentClass.getName()); 164 } 165 catch (IllegalAccessException e) { 166 log.error(e.getCause(), "Could not get service class for %s", componentClass.getName()); 167 } 168 } 169 } 170 if (bean == null && componentName != null && !("".equals(componentName))) 171 bean = springContext.getBean(componentName); 172 173 return bean; 174 } 175 176 @Override 177 @SuppressWarnings("unchecked") 178 public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) { 179 String key = COMPONENT_CLASS_ATTR + componentName; 180 Set<Class<?>> classes = null; 181 GraniteContext context = GraniteContext.getCurrentInstance(); 182 if (context != null) { 183 classes = (Set<Class<?>>)context.getRequestMap().get(key); 184 if (classes != null) 185 return classes; 186 } 187 188 Object bean = findComponent(componentName, componentClass); 189 classes = buildComponentClasses(bean); 190 if (classes == null) 191 return null; 192 193 if (context != null) 194 context.getRequestMap().put(key, classes); 195 return classes; 196 } 197 198 protected Set<Class<?>> buildComponentClasses(Object bean) { 199 Set<Class<?>> classes = new HashSet<Class<?>>(); 200 for (Class<?> i : bean.getClass().getInterfaces()) 201 classes.add(i); 202 203 try { 204 while (bean instanceof Advised) 205 bean = ((Advised)bean).getTargetSource().getTarget(); 206 207 classes.add(AopUtils.getTargetClass(bean)); 208 } 209 catch (Exception e) { 210 log.warn(e, "Could not get AOP class for component " + bean.getClass()); 211 return null; 212 } 213 214 return classes; 215 } 216 217 protected boolean isBeanAnnotationPresent(Object bean, Class<? extends Annotation> annotationClass) { 218 if (bean.getClass().isAnnotationPresent(annotationClass)) 219 return true; 220 221 try { 222 while (bean instanceof Advised) 223 bean = ((Advised)bean).getTargetSource().getTarget(); 224 225 if (AopUtils.getTargetClass(bean).isAnnotationPresent(annotationClass)) 226 return true; 227 } 228 catch (Exception e) { 229 log.warn(e, "Could not get AOP class for component " + bean.getClass()); 230 } 231 232 return false; 233 } 234 235 protected boolean isBeanMethodAnnotationPresent(Object bean, String methodName, Class<?>[] methodArgTypes, Class<? extends Annotation> annotationClass) { 236 try { 237 Method m = bean.getClass().getMethod(methodName, methodArgTypes); 238 if (m.isAnnotationPresent(annotationClass)) 239 return true; 240 241 while (bean instanceof Advised) 242 bean = ((Advised)bean).getTargetSource().getTarget(); 243 244 m = AopUtils.getTargetClass(bean).getMethod(methodName, methodArgTypes); 245 if (m.isAnnotationPresent(annotationClass)) 246 return true; 247 } 248 catch (Exception e) { 249 log.warn("Could not find bean method", e); 250 } 251 252 return false; 253 } 254 255 256 @Override 257 public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) { 258 DataContext.init(); 259 260 DataUpdatePostprocessor dupp = (DataUpdatePostprocessor)findComponent(null, DataUpdatePostprocessor.class); 261 if (dupp != null) 262 DataContext.get().setDataUpdatePostprocessor(dupp); 263 } 264 265 @Override 266 public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) { 267 List<ContextUpdate> results = null; 268 DataContext dataContext = DataContext.get(); 269 Object[][] updates = dataContext != null ? dataContext.getUpdates() : null; 270 271 InvocationResult ires = new InvocationResult(result, results); 272 if (isBeanAnnotationPresent(context.getBean(), BypassTideMerge.class)) 273 ires.setMerge(false); 274 else if (isBeanMethodAnnotationPresent(context.getBean(), context.getMethod().getName(), context.getMethod().getParameterTypes(), BypassTideMerge.class)) 275 ires.setMerge(false); 276 277 ires.setUpdates(updates); 278 279 return ires; 280 } 281 282 @Override 283 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) { 284 } 285 286 287 public void setEntityManagerFactoryBeanName(String beanName) { 288 this.entityManagerFactoryBeanName = beanName; 289 } 290 291 public void setPersistenceManagerBeanName(String beanName) { 292 this.persistenceManagerBeanName = beanName; 293 } 294 295 /** 296 * Create a TidePersistenceManager 297 * 298 * @param create create if not existent (can be false for use in entity merge) 299 * @return a PersistenceContextManager 300 */ 301 @Override 302 protected TidePersistenceManager getTidePersistenceManager(boolean create) { 303 if (!create) 304 return null; 305 306 TidePersistenceManager pm = (TidePersistenceManager)GraniteContext.getCurrentInstance().getRequestMap().get(TidePersistenceManager.class.getName()); 307 if (pm != null) 308 return pm; 309 310 pm = createPersistenceManager(); 311 GraniteContext.getCurrentInstance().getRequestMap().put(TidePersistenceManager.class.getName(), pm); 312 return pm; 313 } 314 315 private TidePersistenceManager createPersistenceManager() { 316 if (persistenceManagerBeanName == null) { 317 if (entityManagerFactoryBeanName == null) { 318 // No bean or entity manager factory specified 319 320 // 1. Look for a TidePersistenceManager bean 321 Map<String, ?> pms = springContext.getBeansOfType(TidePersistenceManager.class); 322 if (pms.size() > 1) 323 throw new RuntimeException("More than one Tide persistence managers defined"); 324 325 if (pms.size() == 1) 326 return (TidePersistenceManager)pms.values().iterator().next(); 327 328 // 2. If not found, try to determine the Spring transaction manager 329 Map<String, ?> tms = springContext.getBeansOfType(PlatformTransactionManager.class); 330 if (tms.isEmpty()) 331 log.debug("No Spring transaction manager found, specify a persistence-manager-bean-name or entity-manager-factory-bean-name"); 332 else if (tms.size() > 1) 333 log.debug("More than one Spring transaction manager found, specify a persistence-manager-bean-name or entity-manager-factory-bean-name"); 334 else if (tms.size() == 1) { 335 PlatformTransactionManager ptm = (PlatformTransactionManager)tms.values().iterator().next(); 336 337 // If no entity manager, we define a Spring persistence manager 338 // that will try to infer persistence info from the Spring transaction manager 339 return new SpringPersistenceManager(ptm); 340 } 341 } 342 343 String emfBeanName = entityManagerFactoryBeanName != null ? entityManagerFactoryBeanName : "entityManagerFactory"; 344 try { 345 // Lookup the specified entity manager factory 346 Object emf = findComponent(emfBeanName, null); 347 348 // Try to determine the Spring transaction manager 349 TideTransactionManager tm = null; 350 Map<String, ?> ptms = springContext.getBeansOfType(PlatformTransactionManager.class); 351 if (ptms.size() == 1) { 352 log.debug("Found Spring transaction manager " + ptms.keySet().iterator().next()); 353 tm = new SpringTransactionManager((PlatformTransactionManager)ptms.values().iterator().next()); 354 } 355 356 Class<?> emfClass = TypeUtil.forName("javax.persistence.EntityManagerFactory"); 357 Class<?> pcmClass = TypeUtil.forName("org.granite.tide.data.JPAPersistenceManager"); 358 Constructor<?>[] cs = pcmClass.getConstructors(); 359 if (tm != null) { 360 for (Constructor<?> c : cs) { 361 if (c.getParameterTypes().length == 2 && emfClass.isAssignableFrom(c.getParameterTypes()[0]) 362 && TideTransactionManager.class.isAssignableFrom(c.getParameterTypes()[1])) { 363 log.debug("Created JPA persistence manager with Spring transaction manager"); 364 return (TidePersistenceManager)c.newInstance(((EntityManagerFactoryInfo)emf).getNativeEntityManagerFactory(), tm); 365 } 366 } 367 } 368 else { 369 for (Constructor<?> c : cs) { 370 if (c.getParameterTypes().length == 1 && emfClass.isAssignableFrom(c.getParameterTypes()[0])) { 371 log.debug("Created default JPA persistence manager"); 372 return (TidePersistenceManager)c.newInstance(emf); 373 } 374 } 375 } 376 377 throw new RuntimeException("Default Tide persistence manager not found"); 378 } 379 catch (ServiceException e) { 380 if (entityManagerFactoryBeanName != null) 381 log.debug("EntityManagerFactory named %s not found, JPA support disabled", emfBeanName); 382 383 return null; 384 } 385 catch (Exception e) { 386 throw new RuntimeException("Could not create default Tide persistence manager", e); 387 } 388 } 389 390 return (TidePersistenceManager)findComponent(persistenceManagerBeanName, null); 391 } 392}