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