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