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 }