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.ejb;
022
023 import java.io.IOException;
024 import java.io.ObjectInputStream;
025 import java.io.ObjectOutputStream;
026 import java.lang.reflect.Method;
027 import java.util.ArrayList;
028 import java.util.HashSet;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.Set;
032 import java.util.concurrent.ConcurrentHashMap;
033
034 import javax.ejb.NoSuchEJBException;
035 import javax.naming.InitialContext;
036 import javax.naming.NamingException;
037 import javax.persistence.EntityManager;
038 import javax.persistence.EntityManagerFactory;
039
040 import org.granite.logging.Logger;
041 import org.granite.messaging.service.EjbServiceMetadata;
042 import org.granite.messaging.service.ServiceException;
043 import org.granite.messaging.service.ServiceInvocationContext;
044 import org.granite.tide.IInvocationCall;
045 import org.granite.tide.IInvocationResult;
046 import org.granite.tide.TidePersistenceManager;
047 import org.granite.tide.TideServiceContext;
048 import org.granite.tide.annotations.BypassTideMerge;
049 import org.granite.tide.async.AsyncPublisher;
050 import org.granite.tide.data.DataContext;
051 import org.granite.tide.data.JPAPersistenceManager;
052 import org.granite.tide.invocation.ContextEvent;
053 import org.granite.tide.invocation.ContextUpdate;
054 import org.granite.tide.invocation.InvocationCall;
055 import org.granite.tide.invocation.InvocationResult;
056 import org.granite.tide.util.AbstractContext;
057
058
059 /**
060 * @author William DRAI
061 */
062 public class EjbServiceContext extends TideServiceContext {
063
064 private static final long serialVersionUID = 1L;
065
066 private static final Logger log = Logger.getLogger(EjbServiceContext.class);
067
068 public static final String CAPITALIZED_DESTINATION_ID = "{capitalized.component.name}";
069 public static final String DESTINATION_ID = "{component.name}";
070
071 private transient Map<String, EjbComponent> ejbLookupCache = new ConcurrentHashMap<String, EjbComponent>();
072 private final Set<String> remoteObservers = new HashSet<String>();
073
074 private final String lookup;
075
076 private final EjbIdentity identity;
077
078 private String entityManagerFactoryJndiName = null;
079 private String entityManagerJndiName = null;
080
081
082 public EjbServiceContext() throws ServiceException {
083 super();
084 lookup = "";
085 identity = new EjbIdentity();
086 }
087
088 public EjbServiceContext(String lookup) throws ServiceException {
089 super();
090 this.lookup = lookup;
091 identity = new EjbIdentity();
092 }
093
094
095 @Override
096 protected AsyncPublisher getAsyncPublisher() {
097 return null;
098 }
099
100
101 public void setEntityManagerFactoryJndiName(String entityManagerFactoryJndiName) {
102 this.entityManagerFactoryJndiName = entityManagerFactoryJndiName;
103 }
104
105 public void setEntityManagerJndiName(String entityManagerJndiName) {
106 this.entityManagerJndiName = entityManagerJndiName;
107 }
108
109 /**
110 * Create a TidePersistenceManager
111 *
112 * @param create create if not existent (can be false for use in entity merge)
113 * @return a TidePersistenceManager
114 */
115 @Override
116 protected TidePersistenceManager getTidePersistenceManager(boolean create) {
117 if (!create)
118 return null;
119
120 EntityManager em = getEntityManager();
121 if (em == null)
122 return null;
123
124 return new JPAPersistenceManager(em);
125 }
126
127
128 /**
129 * Find the entity manager using the jndi names stored in the bean.
130 * @return The found entity manager
131 */
132 private EntityManager getEntityManager() {
133 try {
134 InitialContext jndiContext = new InitialContext();
135
136 if (entityManagerFactoryJndiName != null) {
137 EntityManagerFactory factory = (EntityManagerFactory) jndiContext.lookup(entityManagerFactoryJndiName);
138 return factory.createEntityManager();
139 } else if (entityManagerJndiName != null) {
140 return (EntityManager) jndiContext.lookup(entityManagerJndiName);
141 }
142 } catch (NamingException e) {
143 if (entityManagerFactoryJndiName != null)
144 throw new RuntimeException("Unable to find a EntityManagerFactory for jndiName " + entityManagerFactoryJndiName);
145 else if (entityManagerJndiName != null)
146 throw new RuntimeException("Unable to find a EntityManager for jndiName " + entityManagerJndiName);
147 }
148
149 return null;
150 }
151
152
153 public Object callComponent(Method method, Object... args) throws Exception {
154 String name = method.getDeclaringClass().getSimpleName();
155 name = name.substring(0, 1).toLowerCase() + name.substring(1);
156 if (name.endsWith("Bean"))
157 name = name.substring(0, name.length() - "Bean".length());
158 Object invokee = findComponent(name, null);
159 method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
160 return method.invoke(invokee, args);
161 }
162
163 public Set<String> getRemoteObservers() {
164 return remoteObservers;
165 }
166
167 /* (non-Javadoc)
168 * @see org.granite.tide.ejb.EJBServiceContextIntf#findComponent(java.lang.String)
169 */
170 @Override
171 public Object findComponent(String componentName, Class<?> componentClass) {
172 if ("identity".equals(componentName))
173 return identity;
174
175 EjbComponent component = ejbLookupCache.get(componentName);
176 if (component != null)
177 return component.ejbInstance;
178
179 // Compute EJB JNDI binding.
180 String name = componentName;
181 if (lookup != null) {
182 name = lookup;
183 if (lookup.contains(CAPITALIZED_DESTINATION_ID))
184 name = lookup.replace(CAPITALIZED_DESTINATION_ID, capitalize(componentName));
185 if (lookup.contains(DESTINATION_ID))
186 name = lookup.replace(DESTINATION_ID, componentName);
187 }
188
189 InitialContext ic = null;
190 try {
191 ic = new InitialContext();
192 } catch (Exception e) {
193 throw new ServiceException("Could not get InitialContext", e);
194 }
195
196 log.debug(">> New EjbServiceInvoker looking up: %s", name);
197
198 try {
199 component = new EjbComponent();
200 component.ejbInstance = ic.lookup(name);
201 component.ejbClasses = new HashSet<Class<?>>();
202 Class<?> scannedClass = null;
203 EjbScannedItemHandler itemHandler = EjbScannedItemHandler.instance();
204 for (Class<?> i : component.ejbInstance.getClass().getInterfaces()) {
205 if (itemHandler.getScannedClasses().containsKey(i)) {
206 scannedClass = itemHandler.getScannedClasses().get(i);
207 break;
208 }
209 }
210 if (scannedClass == null)
211 scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass());
212 // GDS-768: handle of proxied no-interface EJBs in GlassFish v3
213 if (scannedClass == null && component.ejbInstance.getClass().getSuperclass() != null)
214 scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass().getSuperclass());
215
216 if (scannedClass != null) {
217 component.ejbClasses.add(scannedClass);
218 for (Map.Entry<Class<?>, Class<?>> me : itemHandler.getScannedClasses().entrySet()) {
219 if (me.getValue().equals(scannedClass))
220 component.ejbClasses.add(me.getKey());
221 }
222 component.ejbMetadata = new EjbServiceMetadata(scannedClass, component.ejbInstance.getClass());
223 }
224 else
225 log.warn("Ejb " + componentName + " was not scanned: remove method will not be called if it is a Stateful bean. Add META-INF/services-config.properties if needed.");
226
227 ejbLookupCache.put(componentName, component);
228
229 return component.ejbInstance;
230 }
231 catch (NamingException e) {
232 log.error("EJB not found " + name + ": " + e.getMessage());
233 throw new ServiceException("Could not lookup for: " + name, e);
234 }
235 }
236
237 /* (non-Javadoc)
238 * @see org.granite.tide.ejb.EJBServiceContextIntf#findComponentClass(java.lang.String)
239 */
240 @Override
241 public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) {
242 if ("identity".equals(componentName)) {
243 Set<Class<?>> classes = new HashSet<Class<?>>(1);
244 classes.add(EjbIdentity.class);
245 return classes;
246 }
247
248 EjbComponent component = ejbLookupCache.get(componentName);
249 if (component == null)
250 findComponent(componentName, componentClass);
251 return ejbLookupCache.get(componentName).ejbClasses;
252 }
253
254 private String capitalize(String s) {
255 if (s == null || s.length() == 0)
256 return s;
257 if (s.length() == 1)
258 return s.toUpperCase();
259 return s.substring(0, 1).toUpperCase() + s.substring(1);
260 }
261
262 /* (non-Javadoc)
263 * @see org.granite.tide.ejb.EJBServiceContextIntf#prepareCall(org.granite.messaging.service.ServiceInvocationContext, org.granite.tide.IInvocationCall, java.lang.String)
264 */
265 @Override
266 public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
267 if ((c instanceof InvocationCall) && ((InvocationCall)c).getListeners() != null)
268 remoteObservers.addAll(((InvocationCall)c).getListeners());
269 Context.create(this);
270
271 // Initialize an empty data context
272 DataContext.init();
273 }
274
275
276 private static class EjbComponent {
277 public Object ejbInstance;
278 public Set<Class<?>> ejbClasses;
279 public EjbServiceMetadata ejbMetadata;
280 }
281
282 /* (non-Javadoc)
283 * @see org.granite.tide.ejb.EJBServiceContextIntf#postCall(org.granite.messaging.service.ServiceInvocationContext, java.lang.Object, java.lang.String)
284 */
285 @Override
286 public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
287 try {
288 AbstractContext threadContext = AbstractContext.instance();
289
290 List<ContextUpdate> results = new ArrayList<ContextUpdate>(threadContext.size());
291 DataContext dataContext = DataContext.get();
292 Object[][] updates = dataContext != null ? dataContext.getUpdates() : null;
293
294 for (Map.Entry<String, Object> entry : threadContext.entrySet())
295 results.add(new ContextUpdate(entry.getKey(), null, entry.getValue(), 3, false));
296
297 InvocationResult ires = new InvocationResult(result, results);
298 if (context.getBean() != null) {
299 if (context.getBean().getClass().isAnnotationPresent(BypassTideMerge.class))
300 ires.setMerge(false);
301 else {
302 try {
303 Method m = context.getBean().getClass().getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
304 if (m.isAnnotationPresent(BypassTideMerge.class))
305 ires.setMerge(false);
306 }
307 catch (Exception e) {
308 log.warn("Could not find bean method", e);
309 }
310 }
311 }
312
313 ires.setUpdates(updates);
314 ires.setEvents(new ArrayList<ContextEvent>(threadContext.getRemoteEvents()));
315
316 if (componentName != null) {
317 EjbComponent component = ejbLookupCache.get(componentName);
318 if (component != null && component.ejbMetadata != null
319 && component.ejbMetadata.isStateful() && component.ejbMetadata.isRemoveMethod(context.getMethod()))
320 ejbLookupCache.remove(componentName);
321 }
322
323 return ires;
324 }
325 finally {
326 AbstractContext.remove();
327 }
328 }
329
330 /* (non-Javadoc)
331 * @see org.granite.tide.ejb.EJBServiceContextIntf#postCallFault(org.granite.messaging.service.ServiceInvocationContext, java.lang.Throwable, java.lang.String)
332 */
333 @Override
334 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
335 try {
336 if (componentName != null) {
337 EjbComponent component = ejbLookupCache.get(componentName);
338 if (t instanceof NoSuchEJBException || (component != null && component.ejbMetadata != null &&
339 (component.ejbMetadata.isStateful() &&
340 component.ejbMetadata.isRemoveMethod(context.getMethod()) &&
341 !component.ejbMetadata.getRetainIfException(context.getMethod()))
342 )) {
343 ejbLookupCache.remove(componentName);
344 }
345 }
346 }
347 finally {
348 AbstractContext.remove();
349 }
350 }
351
352 private void writeObject(ObjectOutputStream out) throws IOException {
353 out.defaultWriteObject();
354 }
355
356 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
357 in.defaultReadObject();
358 ejbLookupCache = new ConcurrentHashMap<String, EjbComponent>();
359 }
360 }