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 }