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