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