/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.tide.spring;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import org.granite.context.GraniteContext;
import org.granite.logging.Logger;
import org.granite.messaging.service.ServiceException;
import org.granite.messaging.service.ServiceInvocationContext;
import org.granite.messaging.webapp.HttpGraniteContext;
import org.granite.tide.IInvocationCall;
import org.granite.tide.IInvocationResult;
import org.granite.tide.TidePersistenceManager;
import org.granite.tide.TideServiceContext;
import org.granite.tide.TideTransactionManager;
import org.granite.tide.annotations.BypassTideMerge;
import org.granite.tide.async.AsyncPublisher;
import org.granite.tide.data.DataContext;
import org.granite.tide.data.DataUpdatePostprocessor;
import org.granite.tide.data.DisableRemoteUpdates;
import org.granite.tide.invocation.ContextUpdate;
import org.granite.tide.invocation.InvocationResult;
import org.granite.util.TypeUtil;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.orm.jpa.EntityManagerFactoryInfo;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* @author Sebastien Deleuze
* @author William DRAI
*/
public class SpringServiceContext extends TideServiceContext implements ApplicationContextAware {
private static final long serialVersionUID = 1L;
protected transient ApplicationContext springContext = null;
private String persistenceManagerBeanName = null;
private String entityManagerFactoryBeanName = null;
private static final Logger log = Logger.getLogger(SpringServiceContext.class);
public SpringServiceContext() throws ServiceException {
super();
log.debug("Getting spring context from container");
getSpringContext();
}
public SpringServiceContext(ApplicationContext springContext) throws ServiceException {
super();
this.springContext = springContext;
}
public void setApplicationContext(ApplicationContext springContext) {
this.springContext = springContext;
}
protected ApplicationContext getSpringContext() {
if (springContext == null) {
GraniteContext context = GraniteContext.getCurrentInstance();
ServletContext sc = ((HttpGraniteContext)context).getServletContext();
springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
}
return springContext;
}
@Override
protected AsyncPublisher getAsyncPublisher() {
return null;
}
@Override
public Object findComponent(String componentName, Class<?> componentClass, String componentPath) {
Object bean = null;
String key = COMPONENT_ATTR + (componentName != null ? componentName : "_CLASS_" + componentClass.getName());
GraniteContext context = GraniteContext.getCurrentInstance();
if (context != null) {
bean = context.getRequestMap().get(key);
if (bean != null)
return bean;
}
try {
bean = internalFindComponent(componentName, componentClass, componentPath);
if (context != null)
context.getRequestMap().put(key, bean);
return bean;
}
catch (NoSuchBeanDefinitionException nexc) {
String msg = "Spring service named '" + componentName + "' does not exist.";
ServiceException e = new ServiceException(msg, nexc);
throw e;
}
catch (BeansException bexc) {
String msg = "Unable to create Spring service named '" + componentName + "'";
ServiceException e = new ServiceException(msg, bexc);
throw e;
}
}
protected Object internalFindComponent(String componentName, Class<?> componentClass, String componentPath) {
Object bean = null;
if (componentClass != null) {
Map<String, ?> beans = springContext.getBeansOfType(componentClass);
if (beans.size() == 1)
bean = beans.values().iterator().next();
else if (beans.size() > 1 && componentName != null && !("".equals(componentName))) {
if (beans.containsKey(componentName))
bean = beans.get(componentName);
}
else if (beans.isEmpty() && springContext.getClass().getName().indexOf("Grails") > 0 && componentClass.getName().endsWith("Service")) {
try {
Object serviceClass = springContext.getBean(componentClass.getName() + "ServiceClass");
Method m = serviceClass.getClass().getMethod("getPropertyName");
String compName = (String)m.invoke(serviceClass);
bean = springContext.getBean(compName);
}
catch (NoSuchMethodException e) {
log.error(e, "Could not get service class for %s", componentClass.getName());
}
catch (InvocationTargetException e) {
log.error(e.getCause(), "Could not get service class for %s", componentClass.getName());
}
catch (IllegalAccessException e) {
log.error(e.getCause(), "Could not get service class for %s", componentClass.getName());
}
}
}
if (bean == null && componentName != null && !("".equals(componentName)))
bean = springContext.getBean(componentName);
return bean;
}
@Override
@SuppressWarnings("unchecked")
public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass, String methodName) {
String key = COMPONENT_CLASS_ATTR + componentName;
Set<Class<?>> classes = null;
GraniteContext context = GraniteContext.getCurrentInstance();
if (context != null) {
classes = (Set<Class<?>>)context.getRequestMap().get(key);
if (classes != null)
return classes;
}
Object bean = findComponent(componentName, componentClass, methodName);
classes = buildComponentClasses(bean);
if (classes == null)
return null;
if (context != null)
context.getRequestMap().put(key, classes);
return classes;
}
protected Set<Class<?>> buildComponentClasses(Object bean) {
Set<Class<?>> classes = new HashSet<Class<?>>();
for (Class<?> i : bean.getClass().getInterfaces())
classes.add(i);
try {
while (bean instanceof Advised)
bean = ((Advised)bean).getTargetSource().getTarget();
classes.add(AopUtils.getTargetClass(bean));
}
catch (Exception e) {
log.warn(e, "Could not get AOP class for component %s", bean.getClass().getName());
return null;
}
return classes;
}
protected boolean isBeanAnnotationPresent(Object bean, Class<? extends Annotation> annotationClass) {
if (bean.getClass().isAnnotationPresent(annotationClass))
return true;
try {
while (bean instanceof Advised)
bean = ((Advised)bean).getTargetSource().getTarget();
if (AopUtils.getTargetClass(bean).isAnnotationPresent(annotationClass))
return true;
}
catch (Exception e) {
log.warn(e, "Could not get AOP class for component %s", bean.getClass().getName());
}
return false;
}
protected boolean isBeanMethodAnnotationPresent(Object bean, String methodName, Class<?>[] methodArgTypes, Class<? extends Annotation> annotationClass) {
try {
Method m = bean.getClass().getMethod(methodName, methodArgTypes);
if (m.isAnnotationPresent(annotationClass))
return true;
while (bean instanceof Advised)
bean = ((Advised)bean).getTargetSource().getTarget();
m = AopUtils.getTargetClass(bean).getMethod(methodName, methodArgTypes);
if (m.isAnnotationPresent(annotationClass))
return true;
}
catch (NoSuchMethodException nsme) {
// Might happen in the AOP target if the method is implemented by a proxy
}
catch (Exception e) {
log.warn(e, "Could not get AOP class for component %s (looking for method %s)", bean.getClass().getName(), methodName);
}
return false;
}
@Override
public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
DataContext.init();
DataUpdatePostprocessor dupp = (DataUpdatePostprocessor)findComponent(null, DataUpdatePostprocessor.class, null);
if (dupp != null)
DataContext.get().setDataUpdatePostprocessor(dupp);
}
@Override
public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
List<ContextUpdate> results = null;
InvocationResult ires = new InvocationResult(result, results);
if (isBeanAnnotationPresent(context.getBean(), BypassTideMerge.class))
ires.setMerge(false);
else if (isBeanMethodAnnotationPresent(context.getBean(), context.getMethod().getName(), context.getMethod().getParameterTypes(), BypassTideMerge.class))
ires.setMerge(false);
if (!isBeanAnnotationPresent(context.getBean(), DisableRemoteUpdates.class) &&
!isBeanMethodAnnotationPresent(context.getBean(), context.getMethod().getName(), context.getMethod().getParameterTypes(), DisableRemoteUpdates.class)) {
DataContext dataContext = DataContext.get();
Object[][] updates = dataContext != null ? dataContext.getUpdates() : null;
ires.setUpdates(updates);
}
return ires;
}
@Override
public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
}
public void setEntityManagerFactoryBeanName(String beanName) {
this.entityManagerFactoryBeanName = beanName;
}
public void setPersistenceManagerBeanName(String beanName) {
this.persistenceManagerBeanName = beanName;
}
/**
* Create a TidePersistenceManager
*
* @param create create if not existent (can be false for use in entity merge)
* @return a PersistenceContextManager
*/
@Override
protected TidePersistenceManager getTidePersistenceManager(boolean create) {
if (!create)
return null;
TidePersistenceManager pm = (TidePersistenceManager)GraniteContext.getCurrentInstance().getRequestMap().get(TidePersistenceManager.class.getName());
if (pm != null)
return pm;
pm = createPersistenceManager();
GraniteContext.getCurrentInstance().getRequestMap().put(TidePersistenceManager.class.getName(), pm);
return pm;
}
private TidePersistenceManager createPersistenceManager() {
if (persistenceManagerBeanName == null) {
if (entityManagerFactoryBeanName == null) {
// No bean or entity manager factory specified
// 1. Look for a TidePersistenceManager bean
Map<String, ?> pms = springContext.getBeansOfType(TidePersistenceManager.class);
if (pms.size() > 1)
throw new RuntimeException("More than one Tide persistence managers defined");
if (pms.size() == 1)
return (TidePersistenceManager)pms.values().iterator().next();
// 2. If not found, try to determine the Spring transaction manager
Map<String, ?> tms = springContext.getBeansOfType(PlatformTransactionManager.class);
if (tms.isEmpty())
log.debug("No Spring transaction manager found, specify a persistence-manager-bean-name or entity-manager-factory-bean-name");
else if (tms.size() > 1)
log.debug("More than one Spring transaction manager found, specify a persistence-manager-bean-name or entity-manager-factory-bean-name");
else if (tms.size() == 1) {
PlatformTransactionManager ptm = (PlatformTransactionManager)tms.values().iterator().next();
// If no entity manager, we define a Spring persistence manager
// that will try to infer persistence info from the Spring transaction manager
return new SpringPersistenceManager(ptm);
}
}
String emfBeanName = entityManagerFactoryBeanName != null ? entityManagerFactoryBeanName : "entityManagerFactory";
try {
// Lookup the specified entity manager factory
Object emf = findComponent(emfBeanName, null, null);
// Try to determine the Spring transaction manager
TideTransactionManager tm = null;
Map<String, ?> ptms = springContext.getBeansOfType(PlatformTransactionManager.class);
if (ptms.size() == 1) {
log.debug("Found Spring transaction manager " + ptms.keySet().iterator().next());
tm = new SpringTransactionManager((PlatformTransactionManager)ptms.values().iterator().next());
}
Class<?> emfClass = TypeUtil.forName("javax.persistence.EntityManagerFactory");
Class<?> pcmClass = TypeUtil.forName("org.granite.tide.data.JPAPersistenceManager");
Constructor<?>[] cs = pcmClass.getConstructors();
if (tm != null) {
for (Constructor<?> c : cs) {
if (c.getParameterTypes().length == 2 && emfClass.isAssignableFrom(c.getParameterTypes()[0])
&& TideTransactionManager.class.isAssignableFrom(c.getParameterTypes()[1])) {
log.debug("Created JPA persistence manager with Spring transaction manager");
return (TidePersistenceManager)c.newInstance(((EntityManagerFactoryInfo)emf).getNativeEntityManagerFactory(), tm);
}
}
}
else {
for (Constructor<?> c : cs) {
if (c.getParameterTypes().length == 1 && emfClass.isAssignableFrom(c.getParameterTypes()[0])) {
log.debug("Created default JPA persistence manager");
return (TidePersistenceManager)c.newInstance(emf);
}
}
}
throw new RuntimeException("Default Tide persistence manager not found");
}
catch (ServiceException e) {
if (entityManagerFactoryBeanName != null)
log.debug("EntityManagerFactory named %s not found, JPA support disabled", emfBeanName);
return null;
}
catch (Exception e) {
throw new RuntimeException("Could not create default Tide persistence manager", e);
}
}
return (TidePersistenceManager)findComponent(persistenceManagerBeanName, null, null);
}
}